hooks/integrity.py
changeset 4835 13b0b96d7982
parent 4530 a3c1549a68c6
child 5007 bc0a67a95b69
equal deleted inserted replaced
4834:b718626a0e60 4835:13b0b96d7982
    33 
    33 
    34     This lock used to avoid potential integrity pb when checking
    34     This lock used to avoid potential integrity pb when checking
    35     RQLUniqueConstraint in two different transactions, as explained in
    35     RQLUniqueConstraint in two different transactions, as explained in
    36     http://intranet.logilab.fr/jpl/ticket/36564
    36     http://intranet.logilab.fr/jpl/ticket/36564
    37     """
    37     """
    38     asession = session.actual_session()
    38     if 'uniquecstrholder' in session.transaction_data:
    39     if 'uniquecstrholder' in asession.transaction_data:
       
    40         return
    39         return
    41     _UNIQUE_CONSTRAINTS_LOCK.acquire()
    40     _UNIQUE_CONSTRAINTS_LOCK.acquire()
    42     asession.transaction_data['uniquecstrholder'] = True
    41     session.transaction_data['uniquecstrholder'] = True
    43     # register operation responsible to release the lock on commit/rollback
    42     # register operation responsible to release the lock on commit/rollback
    44     _ReleaseUniqueConstraintsOperation(asession)
    43     _ReleaseUniqueConstraintsOperation(session)
    45 
    44 
    46 def _release_unique_cstr_lock(session):
    45 def _release_unique_cstr_lock(session):
    47     if 'uniquecstrholder' in session.transaction_data:
    46     if 'uniquecstrholder' in session.transaction_data:
    48         del session.transaction_data['uniquecstrholder']
    47         del session.transaction_data['uniquecstrholder']
    49         _UNIQUE_CONSTRAINTS_LOCK.release()
    48         _UNIQUE_CONSTRAINTS_LOCK.release()
    67         # recheck pending eids
    66         # recheck pending eids
    68         if self.session.deleted_in_transaction(self.eid):
    67         if self.session.deleted_in_transaction(self.eid):
    69             return
    68             return
    70         if self.rtype in self.session.transaction_data.get('pendingrtypes', ()):
    69         if self.rtype in self.session.transaction_data.get('pendingrtypes', ()):
    71             return
    70             return
    72         if self.session.unsafe_execute(*self._rql()).rowcount < 1:
    71         if self.session.execute(*self._rql()).rowcount < 1:
    73             etype = self.session.describe(self.eid)[0]
    72             etype = self.session.describe(self.eid)[0]
    74             _ = self.session._
    73             _ = self.session._
    75             msg = _('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)')
    74             msg = _('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)')
    76             msg %= {'rtype': _(self.rtype), 'etype': _(etype), 'eid': self.eid}
    75             msg %= {'rtype': _(self.rtype), 'etype': _(etype), 'eid': self.eid}
    77             raise ValidationError(self.eid, {self.rtype: msg})
    76             raise ValidationError(self.eid, {self.rtype: msg})
    97 
    96 
    98 class IntegrityHook(hook.Hook):
    97 class IntegrityHook(hook.Hook):
    99     __abstract__ = True
    98     __abstract__ = True
   100     category = 'integrity'
    99     category = 'integrity'
   101 
   100 
   102 class UserIntegrityHook(IntegrityHook):
   101 
   103     __abstract__ = True
   102 class CheckCardinalityHook(IntegrityHook):
   104     __select__ = IntegrityHook.__select__ & hook.regular_session()
       
   105 
       
   106 
       
   107 class CheckCardinalityHook(UserIntegrityHook):
       
   108     """check cardinalities are satisfied"""
   103     """check cardinalities are satisfied"""
   109     __regid__ = 'checkcard'
   104     __regid__ = 'checkcard'
   110     events = ('after_add_entity', 'before_delete_relation')
   105     events = ('after_add_entity', 'before_delete_relation')
   111 
   106 
   112     def __call__(self):
   107     def __call__(self):
   174 
   169 
   175     def commit_event(self):
   170     def commit_event(self):
   176         pass
   171         pass
   177 
   172 
   178 
   173 
   179 class CheckConstraintHook(UserIntegrityHook):
   174 class CheckConstraintHook(IntegrityHook):
   180     """check the relation satisfy its constraints
   175     """check the relation satisfy its constraints
   181 
   176 
   182     this is delayed to a precommit time operation since other relation which
   177     this is delayed to a precommit time operation since other relation which
   183     will make constraint satisfied (or unsatisfied) may be added later.
   178     will make constraint satisfied (or unsatisfied) may be added later.
   184     """
   179     """
   192         if constraints:
   187         if constraints:
   193             _CheckConstraintsOp(self._cw, constraints=constraints,
   188             _CheckConstraintsOp(self._cw, constraints=constraints,
   194                                rdef=(self.eidfrom, self.rtype, self.eidto))
   189                                rdef=(self.eidfrom, self.rtype, self.eidto))
   195 
   190 
   196 
   191 
   197 class CheckAttributeConstraintHook(UserIntegrityHook):
   192 class CheckAttributeConstraintHook(IntegrityHook):
   198     """check the attribute relation satisfy its constraints
   193     """check the attribute relation satisfy its constraints
   199 
   194 
   200     this is delayed to a precommit time operation since other relation which
   195     this is delayed to a precommit time operation since other relation which
   201     will make constraint satisfied (or unsatisfied) may be added later.
   196     will make constraint satisfied (or unsatisfied) may be added later.
   202     """
   197     """
   212                 if constraints:
   207                 if constraints:
   213                     _CheckConstraintsOp(self._cw, constraints=constraints,
   208                     _CheckConstraintsOp(self._cw, constraints=constraints,
   214                                         rdef=(self.entity.eid, attr, None))
   209                                         rdef=(self.entity.eid, attr, None))
   215 
   210 
   216 
   211 
   217 class CheckUniqueHook(UserIntegrityHook):
   212 class CheckUniqueHook(IntegrityHook):
   218     __regid__ = 'checkunique'
   213     __regid__ = 'checkunique'
   219     events = ('before_add_entity', 'before_update_entity')
   214     events = ('before_add_entity', 'before_update_entity')
   220 
   215 
   221     def __call__(self):
   216     def __call__(self):
   222         entity = self.entity
   217         entity = self.entity
   225             if eschema.subjrels[attr].final and eschema.has_unique_values(attr):
   220             if eschema.subjrels[attr].final and eschema.has_unique_values(attr):
   226                 val = entity[attr]
   221                 val = entity[attr]
   227                 if val is None:
   222                 if val is None:
   228                     continue
   223                     continue
   229                 rql = '%s X WHERE X %s %%(val)s' % (entity.e_schema, attr)
   224                 rql = '%s X WHERE X %s %%(val)s' % (entity.e_schema, attr)
   230                 rset = self._cw.unsafe_execute(rql, {'val': val})
   225                 rset = self._cw.execute(rql, {'val': val})
   231                 if rset and rset[0][0] != entity.eid:
   226                 if rset and rset[0][0] != entity.eid:
   232                     msg = self._cw._('the value "%s" is already used, use another one')
   227                     msg = self._cw._('the value "%s" is already used, use another one')
   233                     raise ValidationError(entity.eid, {attr: msg % val})
   228                     raise ValidationError(entity.eid, {attr: msg % val})
   234 
   229 
   235 
   230 
   242         session = self.session
   237         session = self.session
   243         # don't do anything if the entity is being created or deleted
   238         # don't do anything if the entity is being created or deleted
   244         if not (session.deleted_in_transaction(self.eid) or
   239         if not (session.deleted_in_transaction(self.eid) or
   245                 session.added_in_transaction(self.eid)):
   240                 session.added_in_transaction(self.eid)):
   246             etype = session.describe(self.eid)[0]
   241             etype = session.describe(self.eid)[0]
   247             session.unsafe_execute('DELETE %s X WHERE X eid %%(x)s, NOT %s'
   242             session.execute('DELETE %s X WHERE X eid %%(x)s, NOT %s'
   248                                    % (etype, self.relation),
   243                             % (etype, self.relation),
   249                                    {'x': self.eid}, 'x')
   244                             {'x': self.eid}, 'x')
   250 
   245 
   251 
   246 
   252 class DeleteCompositeOrphanHook(IntegrityHook):
   247 class DeleteCompositeOrphanHook(IntegrityHook):
   253     """delete the composed of a composite relation when this relation is deleted
   248     """delete the composed of a composite relation when this relation is deleted
   254     """
   249     """
   288             if oldname == 'owners' and newname != oldname:
   283             if oldname == 'owners' and newname != oldname:
   289                 raise ValidationError(self.entity.eid, {'name': self._cw._('can\'t be changed')})
   284                 raise ValidationError(self.entity.eid, {'name': self._cw._('can\'t be changed')})
   290             self.entity['name'] = newname
   285             self.entity['name'] = newname
   291 
   286 
   292 
   287 
   293 class TidyHtmlFields(UserIntegrityHook):
   288 class TidyHtmlFields(IntegrityHook):
   294     """tidy HTML in rich text strings"""
   289     """tidy HTML in rich text strings"""
   295     __regid__ = 'htmltidy'
   290     __regid__ = 'htmltidy'
   296     events = ('before_add_entity', 'before_update_entity')
   291     events = ('before_add_entity', 'before_update_entity')
   297 
   292 
   298     def __call__(self):
   293     def __call__(self):