hooks/integrity.py
changeset 6142 8bc6eac1fac1
parent 5877 0c7b7b76a84f
child 6376 f8662240ed4d
equal deleted inserted replaced
6141:b8287e54b528 6142:8bc6eac1fac1
    15 #
    15 #
    16 # You should have received a copy of the GNU Lesser General Public License along
    16 # You should have received a copy of the GNU Lesser General Public License along
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    18 """Core hooks: check for data integrity according to the instance'schema
    18 """Core hooks: check for data integrity according to the instance'schema
    19 validity
    19 validity
    20 
       
    21 """
    20 """
       
    21 
    22 __docformat__ = "restructuredtext en"
    22 __docformat__ = "restructuredtext en"
    23 
    23 
    24 from threading import Lock
    24 from threading import Lock
    25 
    25 
    26 from yams.schema import role_name
    26 from yams.schema import role_name
    62     if 'uniquecstrholder' in session.transaction_data:
    62     if 'uniquecstrholder' in session.transaction_data:
    63         del session.transaction_data['uniquecstrholder']
    63         del session.transaction_data['uniquecstrholder']
    64         _UNIQUE_CONSTRAINTS_LOCK.release()
    64         _UNIQUE_CONSTRAINTS_LOCK.release()
    65 
    65 
    66 class _ReleaseUniqueConstraintsOperation(hook.Operation):
    66 class _ReleaseUniqueConstraintsOperation(hook.Operation):
    67     def commit_event(self):
       
    68         pass
       
    69     def postcommit_event(self):
    67     def postcommit_event(self):
    70         _release_unique_cstr_lock(self.session)
    68         _release_unique_cstr_lock(self.session)
    71     def rollback_event(self):
    69     def rollback_event(self):
    72         _release_unique_cstr_lock(self.session)
    70         _release_unique_cstr_lock(self.session)
    73 
    71 
   183                     constraint.repo_check(session, eidfrom, rtype, eidto)
   181                     constraint.repo_check(session, eidfrom, rtype, eidto)
   184                 except NotImplementedError:
   182                 except NotImplementedError:
   185                     self.critical('can\'t check constraint %s, not supported',
   183                     self.critical('can\'t check constraint %s, not supported',
   186                                   constraint)
   184                                   constraint)
   187 
   185 
   188     def commit_event(self):
       
   189         pass
       
   190 
       
   191 
   186 
   192 class CheckConstraintHook(IntegrityHook):
   187 class CheckConstraintHook(IntegrityHook):
   193     """check the relation satisfy its constraints
   188     """check the relation satisfy its constraints
   194 
   189 
   195     this is delayed to a precommit time operation since other relation which
   190     this is delayed to a precommit time operation since other relation which
   217     __regid__ = 'checkattrconstraint'
   212     __regid__ = 'checkattrconstraint'
   218     events = ('after_add_entity', 'after_update_entity')
   213     events = ('after_add_entity', 'after_update_entity')
   219 
   214 
   220     def __call__(self):
   215     def __call__(self):
   221         eschema = self.entity.e_schema
   216         eschema = self.entity.e_schema
   222         for attr in self.entity.edited_attributes:
   217         for attr in self.entity.cw_edited:
   223             if eschema.subjrels[attr].final:
   218             if eschema.subjrels[attr].final:
   224                 constraints = [c for c in eschema.rdef(attr).constraints
   219                 constraints = [c for c in eschema.rdef(attr).constraints
   225                                if isinstance(c, (RQLUniqueConstraint, RQLConstraint))]
   220                                if isinstance(c, (RQLUniqueConstraint, RQLConstraint))]
   226                 if constraints:
   221                 if constraints:
   227                     hook.set_operation(self._cw, 'check_constraints_op',
   222                     hook.set_operation(self._cw, 'check_constraints_op',
   234     events = ('before_add_entity', 'before_update_entity')
   229     events = ('before_add_entity', 'before_update_entity')
   235 
   230 
   236     def __call__(self):
   231     def __call__(self):
   237         entity = self.entity
   232         entity = self.entity
   238         eschema = entity.e_schema
   233         eschema = entity.e_schema
   239         for attr in entity.edited_attributes:
   234         for attr, val in entity.cw_edited.iteritems():
   240             if eschema.subjrels[attr].final and eschema.has_unique_values(attr):
   235             if eschema.subjrels[attr].final and eschema.has_unique_values(attr):
   241                 val = entity[attr]
       
   242                 if val is None:
   236                 if val is None:
   243                     continue
   237                     continue
   244                 rql = '%s X WHERE X %s %%(val)s' % (entity.e_schema, attr)
   238                 rql = '%s X WHERE X %s %%(val)s' % (entity.e_schema, attr)
   245                 rset = self._cw.execute(rql, {'val': val})
   239                 rset = self._cw.execute(rql, {'val': val})
   246                 if rset and rset[0][0] != entity.eid:
   240                 if rset and rset[0][0] != entity.eid:
   255     __regid__ = 'checkownersgroup'
   249     __regid__ = 'checkownersgroup'
   256     __select__ = IntegrityHook.__select__ & is_instance('CWGroup')
   250     __select__ = IntegrityHook.__select__ & is_instance('CWGroup')
   257     events = ('before_delete_entity', 'before_update_entity')
   251     events = ('before_delete_entity', 'before_update_entity')
   258 
   252 
   259     def __call__(self):
   253     def __call__(self):
   260         if self.event == 'before_delete_entity' and self.entity.name == 'owners':
   254         entity = self.entity
       
   255         if self.event == 'before_delete_entity' and entity.name == 'owners':
   261             msg = self._cw._('can\'t be deleted')
   256             msg = self._cw._('can\'t be deleted')
   262             raise ValidationError(self.entity.eid, {None: msg})
   257             raise ValidationError(entity.eid, {None: msg})
   263         elif self.event == 'before_update_entity' and \
   258         elif self.event == 'before_update_entity' \
   264                  'name' in self.entity.edited_attributes:
   259                  and 'name' in entity.cw_edited:
   265             newname = self.entity.pop('name')
   260             oldname, newname = entity.cw_edited.oldnewvalue('name')
   266             oldname = self.entity.name
       
   267             if oldname == 'owners' and newname != oldname:
   261             if oldname == 'owners' and newname != oldname:
   268                 qname = role_name('name', 'subject')
   262                 qname = role_name('name', 'subject')
   269                 msg = self._cw._('can\'t be changed')
   263                 msg = self._cw._('can\'t be changed')
   270                 raise ValidationError(self.entity.eid, {qname: msg})
   264                 raise ValidationError(entity.eid, {qname: msg})
   271             self.entity['name'] = newname
       
   272 
   265 
   273 
   266 
   274 class TidyHtmlFields(IntegrityHook):
   267 class TidyHtmlFields(IntegrityHook):
   275     """tidy HTML in rich text strings"""
   268     """tidy HTML in rich text strings"""
   276     __regid__ = 'htmltidy'
   269     __regid__ = 'htmltidy'
   277     events = ('before_add_entity', 'before_update_entity')
   270     events = ('before_add_entity', 'before_update_entity')
   278 
   271 
   279     def __call__(self):
   272     def __call__(self):
   280         entity = self.entity
   273         entity = self.entity
   281         metaattrs = entity.e_schema.meta_attributes()
   274         metaattrs = entity.e_schema.meta_attributes()
       
   275         edited = entity.cw_edited
   282         for metaattr, (metadata, attr) in metaattrs.iteritems():
   276         for metaattr, (metadata, attr) in metaattrs.iteritems():
   283             if metadata == 'format' and attr in entity.edited_attributes:
   277             if metadata == 'format' and attr in edited:
   284                 try:
   278                 try:
   285                     value = entity[attr]
   279                     value = edited[attr]
   286                 except KeyError:
   280                 except KeyError:
   287                     continue # no text to tidy
   281                     continue # no text to tidy
   288                 if isinstance(value, unicode): # filter out None and Binary
   282                 if isinstance(value, unicode): # filter out None and Binary
   289                     if getattr(entity, str(metaattr)) == 'text/html':
   283                     if getattr(entity, str(metaattr)) == 'text/html':
   290                         entity[attr] = soup2xhtml(value, self._cw.encoding)
   284                         edited[attr] = soup2xhtml(value, self._cw.encoding)
   291 
   285 
   292 
   286 
   293 class StripCWUserLoginHook(IntegrityHook):
   287 class StripCWUserLoginHook(IntegrityHook):
   294     """ensure user logins are stripped"""
   288     """ensure user logins are stripped"""
   295     __regid__ = 'stripuserlogin'
   289     __regid__ = 'stripuserlogin'
   296     __select__ = IntegrityHook.__select__ & is_instance('CWUser')
   290     __select__ = IntegrityHook.__select__ & is_instance('CWUser')
   297     events = ('before_add_entity', 'before_update_entity',)
   291     events = ('before_add_entity', 'before_update_entity',)
   298 
   292 
   299     def __call__(self):
   293     def __call__(self):
   300         user = self.entity
   294         login = self.entity.cw_edited.get('login')
   301         if 'login' in user.edited_attributes and user.login:
   295         if login:
   302             user.login = user.login.strip()
   296             self.entity.cw_edited['login'] = login.strip()
   303 
   297 
   304 
   298 
   305 # 'active' integrity hooks: you usually don't want to deactivate them, they are
   299 # 'active' integrity hooks: you usually don't want to deactivate them, they are
   306 # not really integrity check, they maintain consistency on changes
   300 # not really integrity check, they maintain consistency on changes
   307 
   301