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): |