91 _LOGGER = getLogger('cubicweb.schemaloader') |
101 _LOGGER = getLogger('cubicweb.schemaloader') |
92 |
102 |
93 # entity and relation schema created from serialized schema have an eid |
103 # entity and relation schema created from serialized schema have an eid |
94 ybo.ETYPE_PROPERTIES += ('eid',) |
104 ybo.ETYPE_PROPERTIES += ('eid',) |
95 ybo.RTYPE_PROPERTIES += ('eid',) |
105 ybo.RTYPE_PROPERTIES += ('eid',) |
96 |
|
97 PUB_SYSTEM_ENTITY_PERMS = { |
|
98 'read': ('managers', 'users', 'guests',), |
|
99 'add': ('managers',), |
|
100 'delete': ('managers',), |
|
101 'update': ('managers',), |
|
102 } |
|
103 PUB_SYSTEM_REL_PERMS = { |
|
104 'read': ('managers', 'users', 'guests',), |
|
105 'add': ('managers',), |
|
106 'delete': ('managers',), |
|
107 } |
|
108 PUB_SYSTEM_ATTR_PERMS = { |
|
109 'read': ('managers', 'users', 'guests',), |
|
110 'update': ('managers',), |
|
111 } |
|
112 RO_REL_PERMS = { |
|
113 'read': ('managers', 'users', 'guests',), |
|
114 'add': (), |
|
115 'delete': (), |
|
116 } |
|
117 RO_ATTR_PERMS = { |
|
118 'read': ('managers', 'users', 'guests',), |
|
119 'update': (), |
|
120 } |
|
121 |
|
122 # XXX same algorithm as in reorder_cubes and probably other place, |
|
123 # may probably extract a generic function |
|
124 def order_eschemas(eschemas): |
|
125 """return entity schemas ordered such that entity types which specializes an |
|
126 other one appears after that one |
|
127 """ |
|
128 graph = {} |
|
129 for eschema in eschemas: |
|
130 if eschema.specializes(): |
|
131 graph[eschema] = set((eschema.specializes(),)) |
|
132 else: |
|
133 graph[eschema] = set() |
|
134 cycles = get_cycles(graph) |
|
135 if cycles: |
|
136 cycles = '\n'.join(' -> '.join(cycle) for cycle in cycles) |
|
137 raise Exception('cycles in entity schema specialization: %s' |
|
138 % cycles) |
|
139 eschemas = [] |
|
140 while graph: |
|
141 # sorted to get predictable results |
|
142 for eschema, deps in sorted(graph.items()): |
|
143 if not deps: |
|
144 eschemas.append(eschema) |
|
145 del graph[eschema] |
|
146 for deps in graph.itervalues(): |
|
147 try: |
|
148 deps.remove(eschema) |
|
149 except KeyError: |
|
150 continue |
|
151 return eschemas |
|
152 |
|
153 def bw_normalize_etype(etype): |
|
154 if etype in ETYPE_NAME_MAP: |
|
155 msg = '%s has been renamed to %s, please update your code' % ( |
|
156 etype, ETYPE_NAME_MAP[etype]) |
|
157 warn(msg, DeprecationWarning, stacklevel=4) |
|
158 etype = ETYPE_NAME_MAP[etype] |
|
159 return etype |
|
160 |
|
161 def display_name(req, key, form='', context=None): |
|
162 """return a internationalized string for the key (schema entity or relation |
|
163 name) in a given form |
|
164 """ |
|
165 assert form in ('', 'plural', 'subject', 'object') |
|
166 if form == 'subject': |
|
167 form = '' |
|
168 if form: |
|
169 key = key + '_' + form |
|
170 # ensure unicode |
|
171 if context is not None: |
|
172 return unicode(req.pgettext(context, key)) |
|
173 else: |
|
174 return unicode(req._(key)) |
|
175 |
|
176 |
|
177 # Schema objects definition ################################################### |
|
178 |
|
179 def ERSchema_display_name(self, req, form='', context=None): |
|
180 """return a internationalized string for the entity/relation type name in |
|
181 a given form |
|
182 """ |
|
183 return display_name(req, self.type, form, context) |
|
184 ERSchema.display_name = ERSchema_display_name |
|
185 |
|
186 @cached |
|
187 def get_groups(self, action): |
|
188 """return the groups authorized to perform <action> on entities of |
|
189 this type |
|
190 |
|
191 :type action: str |
|
192 :param action: the name of a permission |
|
193 |
|
194 :rtype: tuple |
|
195 :return: names of the groups with the given permission |
|
196 """ |
|
197 assert action in self.ACTIONS, action |
|
198 #assert action in self._groups, '%s %s' % (self, action) |
|
199 try: |
|
200 return frozenset(g for g in self.permissions[action] if isinstance(g, basestring)) |
|
201 except KeyError: |
|
202 return () |
|
203 PermissionMixIn.get_groups = get_groups |
|
204 |
|
205 @cached |
|
206 def get_rqlexprs(self, action): |
|
207 """return the rql expressions representing queries to check the user is allowed |
|
208 to perform <action> on entities of this type |
|
209 |
|
210 :type action: str |
|
211 :param action: the name of a permission |
|
212 |
|
213 :rtype: tuple |
|
214 :return: the rql expressions with the given permission |
|
215 """ |
|
216 assert action in self.ACTIONS, action |
|
217 #assert action in self._rqlexprs, '%s %s' % (self, action) |
|
218 try: |
|
219 return tuple(g for g in self.permissions[action] if not isinstance(g, basestring)) |
|
220 except KeyError: |
|
221 return () |
|
222 PermissionMixIn.get_rqlexprs = get_rqlexprs |
|
223 |
|
224 orig_set_action_permissions = PermissionMixIn.set_action_permissions |
|
225 def set_action_permissions(self, action, permissions): |
|
226 """set the groups and rql expressions allowing to perform <action> on |
|
227 entities of this type |
|
228 |
|
229 :type action: str |
|
230 :param action: the name of a permission |
|
231 |
|
232 :type permissions: tuple |
|
233 :param permissions: the groups and rql expressions allowing the given action |
|
234 """ |
|
235 orig_set_action_permissions(self, action, tuple(permissions)) |
|
236 clear_cache(self, 'get_rqlexprs') |
|
237 clear_cache(self, 'get_groups') |
|
238 PermissionMixIn.set_action_permissions = set_action_permissions |
|
239 |
|
240 def has_local_role(self, action): |
|
241 """return true if the action *may* be granted localy (eg either rql |
|
242 expressions or the owners group are used in security definition) |
|
243 |
|
244 XXX this method is only there since we don't know well how to deal with |
|
245 'add' action checking. Also find a better name would be nice. |
|
246 """ |
|
247 assert action in self.ACTIONS, action |
|
248 if self.get_rqlexprs(action): |
|
249 return True |
|
250 if action in ('update', 'delete'): |
|
251 return 'owners' in self.get_groups(action) |
|
252 return False |
|
253 PermissionMixIn.has_local_role = has_local_role |
|
254 |
|
255 def may_have_permission(self, action, req): |
|
256 if action != 'read' and not (self.has_local_role('read') or |
|
257 self.has_perm(req, 'read')): |
|
258 return False |
|
259 return self.has_local_role(action) or self.has_perm(req, action) |
|
260 PermissionMixIn.may_have_permission = may_have_permission |
|
261 |
|
262 def has_perm(self, _cw, action, **kwargs): |
|
263 """return true if the action is granted globaly or localy""" |
|
264 try: |
|
265 self.check_perm(_cw, action, **kwargs) |
|
266 return True |
|
267 except Unauthorized: |
|
268 return False |
|
269 PermissionMixIn.has_perm = has_perm |
|
270 |
|
271 def check_perm(self, _cw, action, **kwargs): |
|
272 # NB: _cw may be a server transaction or a request object. |
|
273 # |
|
274 # check user is in an allowed group, if so that's enough internal |
|
275 # transactions should always stop there |
|
276 groups = self.get_groups(action) |
|
277 if _cw.user.matching_groups(groups): |
|
278 return |
|
279 # if 'owners' in allowed groups, check if the user actually owns this |
|
280 # object, if so that's enough |
|
281 # |
|
282 # NB: give _cw to user.owns since user is not be bound to a transaction on |
|
283 # the repository side |
|
284 if 'owners' in groups and ( |
|
285 kwargs.get('creating') |
|
286 or ('eid' in kwargs and _cw.user.owns(kwargs['eid']))): |
|
287 return |
|
288 # else if there is some rql expressions, check them |
|
289 if any(rqlexpr.check(_cw, **kwargs) |
|
290 for rqlexpr in self.get_rqlexprs(action)): |
|
291 return |
|
292 raise Unauthorized(action, str(self)) |
|
293 PermissionMixIn.check_perm = check_perm |
|
294 |
|
295 |
|
296 RelationDefinitionSchema._RPROPERTIES['eid'] = None |
|
297 # remember rproperties defined at this point. Others will have to be serialized in |
|
298 # CWAttribute.extra_props |
|
299 KNOWN_RPROPERTIES = RelationDefinitionSchema.ALL_PROPERTIES() |
|
300 |
|
301 def rql_expression(self, expression, mainvars=None, eid=None): |
|
302 """rql expression factory""" |
|
303 if self.rtype.final: |
|
304 return ERQLExpression(expression, mainvars, eid) |
|
305 return RRQLExpression(expression, mainvars, eid) |
|
306 RelationDefinitionSchema.rql_expression = rql_expression |
|
307 |
|
308 orig_check_permission_definitions = RelationDefinitionSchema.check_permission_definitions |
|
309 def check_permission_definitions(self): |
|
310 orig_check_permission_definitions(self) |
|
311 schema = self.subject.schema |
|
312 for action, groups in self.permissions.iteritems(): |
|
313 for group_or_rqlexpr in groups: |
|
314 if action == 'read' and \ |
|
315 isinstance(group_or_rqlexpr, RQLExpression): |
|
316 msg = "can't use rql expression for read permission of %s" |
|
317 raise BadSchemaDefinition(msg % self) |
|
318 if self.final and isinstance(group_or_rqlexpr, RRQLExpression): |
|
319 msg = "can't use RRQLExpression on %s, use an ERQLExpression" |
|
320 raise BadSchemaDefinition(msg % self) |
|
321 if not self.final and isinstance(group_or_rqlexpr, ERQLExpression): |
|
322 msg = "can't use ERQLExpression on %s, use a RRQLExpression" |
|
323 raise BadSchemaDefinition(msg % self) |
|
324 RelationDefinitionSchema.check_permission_definitions = check_permission_definitions |
|
325 |
|
326 |
|
327 class CubicWebEntitySchema(EntitySchema): |
|
328 """a entity has a type, a set of subject and or object relations |
|
329 the entity schema defines the possible relations for a given type and some |
|
330 constraints on those relations |
|
331 """ |
|
332 def __init__(self, schema=None, edef=None, eid=None, **kwargs): |
|
333 super(CubicWebEntitySchema, self).__init__(schema, edef, **kwargs) |
|
334 if eid is None and edef is not None: |
|
335 eid = getattr(edef, 'eid', None) |
|
336 self.eid = eid |
|
337 |
|
338 def check_permission_definitions(self): |
|
339 super(CubicWebEntitySchema, self).check_permission_definitions() |
|
340 for groups in self.permissions.itervalues(): |
|
341 for group_or_rqlexpr in groups: |
|
342 if isinstance(group_or_rqlexpr, RRQLExpression): |
|
343 msg = "can't use RRQLExpression on %s, use an ERQLExpression" |
|
344 raise BadSchemaDefinition(msg % self.type) |
|
345 |
|
346 def is_subobject(self, strict=False, skiprels=None): |
|
347 if skiprels is None: |
|
348 skiprels = SKIP_COMPOSITE_RELS |
|
349 else: |
|
350 skiprels += SKIP_COMPOSITE_RELS |
|
351 return super(CubicWebEntitySchema, self).is_subobject(strict, |
|
352 skiprels=skiprels) |
|
353 |
|
354 def attribute_definitions(self): |
|
355 """return an iterator on attribute definitions |
|
356 |
|
357 attribute relations are a subset of subject relations where the |
|
358 object's type is a final entity |
|
359 |
|
360 an attribute definition is a 2-uple : |
|
361 * name of the relation |
|
362 * schema of the destination entity type |
|
363 """ |
|
364 iter = super(CubicWebEntitySchema, self).attribute_definitions() |
|
365 for rschema, attrschema in iter: |
|
366 if rschema.type == 'has_text': |
|
367 continue |
|
368 yield rschema, attrschema |
|
369 |
|
370 def main_attribute(self): |
|
371 """convenience method that returns the *main* (i.e. the first non meta) |
|
372 attribute defined in the entity schema |
|
373 """ |
|
374 for rschema, _ in self.attribute_definitions(): |
|
375 if not (rschema in META_RTYPES |
|
376 or self.is_metadata(rschema)): |
|
377 return rschema |
|
378 |
|
379 def add_subject_relation(self, rschema): |
|
380 """register the relation schema as possible subject relation""" |
|
381 super(CubicWebEntitySchema, self).add_subject_relation(rschema) |
|
382 if rschema.final: |
|
383 if self.rdef(rschema).get('fulltextindexed'): |
|
384 self._update_has_text() |
|
385 elif rschema.fulltext_container: |
|
386 self._update_has_text() |
|
387 |
|
388 def add_object_relation(self, rschema): |
|
389 """register the relation schema as possible object relation""" |
|
390 super(CubicWebEntitySchema, self).add_object_relation(rschema) |
|
391 if rschema.fulltext_container: |
|
392 self._update_has_text() |
|
393 |
|
394 def del_subject_relation(self, rtype): |
|
395 super(CubicWebEntitySchema, self).del_subject_relation(rtype) |
|
396 if 'has_text' in self.subjrels: |
|
397 self._update_has_text(deletion=True) |
|
398 |
|
399 def del_object_relation(self, rtype): |
|
400 super(CubicWebEntitySchema, self).del_object_relation(rtype) |
|
401 if 'has_text' in self.subjrels: |
|
402 self._update_has_text(deletion=True) |
|
403 |
|
404 def _update_has_text(self, deletion=False): |
|
405 may_need_has_text, has_has_text = False, False |
|
406 need_has_text = None |
|
407 for rschema in self.subject_relations(): |
|
408 if rschema.final: |
|
409 if rschema == 'has_text': |
|
410 has_has_text = True |
|
411 elif self.rdef(rschema).get('fulltextindexed'): |
|
412 may_need_has_text = True |
|
413 elif rschema.fulltext_container: |
|
414 if rschema.fulltext_container == 'subject': |
|
415 may_need_has_text = True |
|
416 else: |
|
417 need_has_text = False |
|
418 for rschema in self.object_relations(): |
|
419 if rschema.fulltext_container: |
|
420 if rschema.fulltext_container == 'object': |
|
421 may_need_has_text = True |
|
422 else: |
|
423 need_has_text = False |
|
424 if need_has_text is None: |
|
425 need_has_text = may_need_has_text |
|
426 if need_has_text and not has_has_text and not deletion: |
|
427 rdef = ybo.RelationDefinition(self.type, 'has_text', 'String', |
|
428 __permissions__=RO_ATTR_PERMS) |
|
429 self.schema.add_relation_def(rdef) |
|
430 elif not need_has_text and has_has_text: |
|
431 # use rschema.del_relation_def and not schema.del_relation_def to |
|
432 # avoid deleting the relation type accidentally... |
|
433 self.schema['has_text'].del_relation_def(self, self.schema['String']) |
|
434 |
|
435 def schema_entity(self): # XXX @property for consistency with meta |
|
436 """return True if this entity type is used to build the schema""" |
|
437 return self.type in SCHEMA_TYPES |
|
438 |
|
439 def rql_expression(self, expression, mainvars=None, eid=None): |
|
440 """rql expression factory""" |
|
441 return ERQLExpression(expression, mainvars, eid) |
|
442 |
|
443 |
|
444 class CubicWebRelationSchema(RelationSchema): |
|
445 |
|
446 def __init__(self, schema=None, rdef=None, eid=None, **kwargs): |
|
447 if rdef is not None: |
|
448 # if this relation is inlined |
|
449 self.inlined = rdef.inlined |
|
450 super(CubicWebRelationSchema, self).__init__(schema, rdef, **kwargs) |
|
451 if eid is None and rdef is not None: |
|
452 eid = getattr(rdef, 'eid', None) |
|
453 self.eid = eid |
|
454 |
|
455 @property |
|
456 def meta(self): |
|
457 return self.type in META_RTYPES |
|
458 |
|
459 def schema_relation(self): # XXX @property for consistency with meta |
|
460 """return True if this relation type is used to build the schema""" |
|
461 return self.type in SCHEMA_TYPES |
|
462 |
|
463 def may_have_permission(self, action, req, eschema=None, role=None): |
|
464 if eschema is not None: |
|
465 for tschema in self.targets(eschema, role): |
|
466 rdef = self.role_rdef(eschema, tschema, role) |
|
467 if rdef.may_have_permission(action, req): |
|
468 return True |
|
469 else: |
|
470 for rdef in self.rdefs.itervalues(): |
|
471 if rdef.may_have_permission(action, req): |
|
472 return True |
|
473 return False |
|
474 |
|
475 def has_perm(self, _cw, action, **kwargs): |
|
476 """return true if the action is granted globaly or localy""" |
|
477 if self.final: |
|
478 assert not ('fromeid' in kwargs or 'toeid' in kwargs), kwargs |
|
479 assert action in ('read', 'update') |
|
480 if 'eid' in kwargs: |
|
481 subjtype = _cw.describe(kwargs['eid'])[0] |
|
482 else: |
|
483 subjtype = objtype = None |
|
484 else: |
|
485 assert not 'eid' in kwargs, kwargs |
|
486 assert action in ('read', 'add', 'delete') |
|
487 if 'fromeid' in kwargs: |
|
488 subjtype = _cw.describe(kwargs['fromeid'])[0] |
|
489 elif 'frometype' in kwargs: |
|
490 subjtype = kwargs.pop('frometype') |
|
491 else: |
|
492 subjtype = None |
|
493 if 'toeid' in kwargs: |
|
494 objtype = _cw.describe(kwargs['toeid'])[0] |
|
495 elif 'toetype' in kwargs: |
|
496 objtype = kwargs.pop('toetype') |
|
497 else: |
|
498 objtype = None |
|
499 if objtype and subjtype: |
|
500 return self.rdef(subjtype, objtype).has_perm(_cw, action, **kwargs) |
|
501 elif subjtype: |
|
502 for tschema in self.targets(subjtype, 'subject'): |
|
503 rdef = self.rdef(subjtype, tschema) |
|
504 if not rdef.has_perm(_cw, action, **kwargs): |
|
505 return False |
|
506 elif objtype: |
|
507 for tschema in self.targets(objtype, 'object'): |
|
508 rdef = self.rdef(tschema, objtype) |
|
509 if not rdef.has_perm(_cw, action, **kwargs): |
|
510 return False |
|
511 else: |
|
512 for rdef in self.rdefs.itervalues(): |
|
513 if not rdef.has_perm(_cw, action, **kwargs): |
|
514 return False |
|
515 return True |
|
516 |
|
517 @deprecated('use .rdef(subjtype, objtype).role_cardinality(role)') |
|
518 def cardinality(self, subjtype, objtype, target): |
|
519 return self.rdef(subjtype, objtype).role_cardinality(target) |
|
520 |
|
521 |
|
522 class CubicWebSchema(Schema): |
|
523 """set of entities and relations schema defining the possible data sets |
|
524 used in an application |
|
525 |
|
526 :type name: str |
|
527 :ivar name: name of the schema, usually the instance identifier |
|
528 |
|
529 :type base: str |
|
530 :ivar base: path of the directory where the schema is defined |
|
531 """ |
|
532 reading_from_database = False |
|
533 entity_class = CubicWebEntitySchema |
|
534 relation_class = CubicWebRelationSchema |
|
535 no_specialization_inference = ('identity',) |
|
536 |
|
537 def __init__(self, *args, **kwargs): |
|
538 self._eid_index = {} |
|
539 super(CubicWebSchema, self).__init__(*args, **kwargs) |
|
540 ybo.register_base_types(self) |
|
541 rschema = self.add_relation_type(ybo.RelationType('eid')) |
|
542 rschema.final = True |
|
543 rschema = self.add_relation_type(ybo.RelationType('has_text')) |
|
544 rschema.final = True |
|
545 rschema = self.add_relation_type(ybo.RelationType('identity')) |
|
546 rschema.final = False |
|
547 |
|
548 etype_name_re = r'[A-Z][A-Za-z0-9]*[a-z]+[A-Za-z0-9]*$' |
|
549 def add_entity_type(self, edef): |
|
550 edef.name = edef.name.encode() |
|
551 edef.name = bw_normalize_etype(edef.name) |
|
552 if not re.match(self.etype_name_re, edef.name): |
|
553 raise BadSchemaDefinition( |
|
554 '%r is not a valid name for an entity type. It should start ' |
|
555 'with an upper cased letter and be followed by at least a ' |
|
556 'lower cased letter' % edef.name) |
|
557 eschema = super(CubicWebSchema, self).add_entity_type(edef) |
|
558 if not eschema.final: |
|
559 # automatically add the eid relation to non final entity types |
|
560 rdef = ybo.RelationDefinition(eschema.type, 'eid', 'Int', |
|
561 cardinality='11', uid=True, |
|
562 __permissions__=RO_ATTR_PERMS) |
|
563 self.add_relation_def(rdef) |
|
564 rdef = ybo.RelationDefinition(eschema.type, 'identity', eschema.type, |
|
565 __permissions__=RO_REL_PERMS) |
|
566 self.add_relation_def(rdef) |
|
567 self._eid_index[eschema.eid] = eschema |
|
568 return eschema |
|
569 |
|
570 def add_relation_type(self, rdef): |
|
571 if not rdef.name.islower(): |
|
572 raise BadSchemaDefinition( |
|
573 '%r is not a valid name for a relation type. It should be ' |
|
574 'lower cased' % rdef.name) |
|
575 rdef.name = rdef.name.encode() |
|
576 rschema = super(CubicWebSchema, self).add_relation_type(rdef) |
|
577 self._eid_index[rschema.eid] = rschema |
|
578 return rschema |
|
579 |
|
580 def add_relation_def(self, rdef): |
|
581 """build a part of a relation schema |
|
582 (i.e. add a relation between two specific entity's types) |
|
583 |
|
584 :type subject: str |
|
585 :param subject: entity's type that is subject of the relation |
|
586 |
|
587 :type rtype: str |
|
588 :param rtype: the relation's type (i.e. the name of the relation) |
|
589 |
|
590 :type obj: str |
|
591 :param obj: entity's type that is object of the relation |
|
592 |
|
593 :rtype: RelationSchema |
|
594 :param: the newly created or just completed relation schema |
|
595 """ |
|
596 rdef.name = rdef.name.lower() |
|
597 rdef.subject = bw_normalize_etype(rdef.subject) |
|
598 rdef.object = bw_normalize_etype(rdef.object) |
|
599 rdefs = super(CubicWebSchema, self).add_relation_def(rdef) |
|
600 if rdefs: |
|
601 try: |
|
602 self._eid_index[rdef.eid] = rdefs |
|
603 except AttributeError: |
|
604 pass # not a serialized schema |
|
605 return rdefs |
|
606 |
|
607 def del_relation_type(self, rtype): |
|
608 rschema = self.rschema(rtype) |
|
609 self._eid_index.pop(rschema.eid, None) |
|
610 super(CubicWebSchema, self).del_relation_type(rtype) |
|
611 |
|
612 def del_relation_def(self, subjtype, rtype, objtype): |
|
613 for k, v in self._eid_index.items(): |
|
614 if not isinstance(v, RelationDefinitionSchema): |
|
615 continue |
|
616 if v.subject == subjtype and v.rtype == rtype and v.object == objtype: |
|
617 del self._eid_index[k] |
|
618 break |
|
619 super(CubicWebSchema, self).del_relation_def(subjtype, rtype, objtype) |
|
620 |
|
621 def del_entity_type(self, etype): |
|
622 eschema = self.eschema(etype) |
|
623 self._eid_index.pop(eschema.eid, None) |
|
624 # deal with has_text first, else its automatic deletion (see above) |
|
625 # may trigger an error in ancestor's del_entity_type method |
|
626 if 'has_text' in eschema.subject_relations(): |
|
627 self.del_relation_def(etype, 'has_text', 'String') |
|
628 super(CubicWebSchema, self).del_entity_type(etype) |
|
629 |
|
630 def schema_by_eid(self, eid): |
|
631 return self._eid_index[eid] |
|
632 |
106 |
633 # Bases for manipulating RQL in schema ######################################### |
107 # Bases for manipulating RQL in schema ######################################### |
634 |
108 |
635 def guess_rrqlexpr_mainvars(expression): |
109 def guess_rrqlexpr_mainvars(expression): |
636 defined = set(split_expression(expression)) |
110 defined = set(split_expression(expression)) |
918 return False |
396 return False |
919 kwargs['o'] = toeid |
397 kwargs['o'] = toeid |
920 return self._check(_cw, **kwargs) |
398 return self._check(_cw, **kwargs) |
921 |
399 |
922 |
400 |
923 # in yams, default 'update' perm for attributes granted to managers and owners. |
401 # In yams, default 'update' perm for attributes granted to managers and owners. |
924 # Within cw, we want to default to users who may edit the entity holding the |
402 # Within cw, we want to default to users who may edit the entity holding the |
925 # attribute. |
403 # attribute. |
926 ybo.DEFAULT_ATTRPERMS['update'] = ( |
404 # These default permissions won't be checked by the security hooks: |
927 'managers', ERQLExpression('U has_update_permission X')) |
405 # since they delegate checking to the entity, we can skip actual checks. |
|
406 ybo.DEFAULT_ATTRPERMS['update'] = ('managers', ERQLExpression('U has_update_permission X')) |
|
407 ybo.DEFAULT_ATTRPERMS['add'] = ('managers', ERQLExpression('U has_add_permission X')) |
|
408 |
|
409 |
|
410 PUB_SYSTEM_ENTITY_PERMS = { |
|
411 'read': ('managers', 'users', 'guests',), |
|
412 'add': ('managers',), |
|
413 'delete': ('managers',), |
|
414 'update': ('managers',), |
|
415 } |
|
416 PUB_SYSTEM_REL_PERMS = { |
|
417 'read': ('managers', 'users', 'guests',), |
|
418 'add': ('managers',), |
|
419 'delete': ('managers',), |
|
420 } |
|
421 PUB_SYSTEM_ATTR_PERMS = { |
|
422 'read': ('managers', 'users', 'guests',), |
|
423 'add': ('managers',), |
|
424 'update': ('managers',), |
|
425 } |
|
426 RO_REL_PERMS = { |
|
427 'read': ('managers', 'users', 'guests',), |
|
428 'add': (), |
|
429 'delete': (), |
|
430 } |
|
431 RO_ATTR_PERMS = { |
|
432 'read': ('managers', 'users', 'guests',), |
|
433 'add': ybo.DEFAULT_ATTRPERMS['add'], |
|
434 'update': (), |
|
435 } |
|
436 |
|
437 # XXX same algorithm as in reorder_cubes and probably other place, |
|
438 # may probably extract a generic function |
|
439 def order_eschemas(eschemas): |
|
440 """return entity schemas ordered such that entity types which specializes an |
|
441 other one appears after that one |
|
442 """ |
|
443 graph = {} |
|
444 for eschema in eschemas: |
|
445 if eschema.specializes(): |
|
446 graph[eschema] = set((eschema.specializes(),)) |
|
447 else: |
|
448 graph[eschema] = set() |
|
449 cycles = get_cycles(graph) |
|
450 if cycles: |
|
451 cycles = '\n'.join(' -> '.join(cycle) for cycle in cycles) |
|
452 raise Exception('cycles in entity schema specialization: %s' |
|
453 % cycles) |
|
454 eschemas = [] |
|
455 while graph: |
|
456 # sorted to get predictable results |
|
457 for eschema, deps in sorted(graph.items()): |
|
458 if not deps: |
|
459 eschemas.append(eschema) |
|
460 del graph[eschema] |
|
461 for deps in graph.itervalues(): |
|
462 try: |
|
463 deps.remove(eschema) |
|
464 except KeyError: |
|
465 continue |
|
466 return eschemas |
|
467 |
|
468 def bw_normalize_etype(etype): |
|
469 if etype in ETYPE_NAME_MAP: |
|
470 msg = '%s has been renamed to %s, please update your code' % ( |
|
471 etype, ETYPE_NAME_MAP[etype]) |
|
472 warn(msg, DeprecationWarning, stacklevel=4) |
|
473 etype = ETYPE_NAME_MAP[etype] |
|
474 return etype |
|
475 |
|
476 def display_name(req, key, form='', context=None): |
|
477 """return a internationalized string for the key (schema entity or relation |
|
478 name) in a given form |
|
479 """ |
|
480 assert form in ('', 'plural', 'subject', 'object') |
|
481 if form == 'subject': |
|
482 form = '' |
|
483 if form: |
|
484 key = key + '_' + form |
|
485 # ensure unicode |
|
486 if context is not None: |
|
487 return unicode(req.pgettext(context, key)) |
|
488 else: |
|
489 return unicode(req._(key)) |
|
490 |
|
491 |
|
492 # Schema objects definition ################################################### |
|
493 |
|
494 def ERSchema_display_name(self, req, form='', context=None): |
|
495 """return a internationalized string for the entity/relation type name in |
|
496 a given form |
|
497 """ |
|
498 return display_name(req, self.type, form, context) |
|
499 ERSchema.display_name = ERSchema_display_name |
|
500 |
|
501 @cached |
|
502 def get_groups(self, action): |
|
503 """return the groups authorized to perform <action> on entities of |
|
504 this type |
|
505 |
|
506 :type action: str |
|
507 :param action: the name of a permission |
|
508 |
|
509 :rtype: tuple |
|
510 :return: names of the groups with the given permission |
|
511 """ |
|
512 assert action in self.ACTIONS, action |
|
513 #assert action in self._groups, '%s %s' % (self, action) |
|
514 try: |
|
515 return frozenset(g for g in self.permissions[action] if isinstance(g, basestring)) |
|
516 except KeyError: |
|
517 return () |
|
518 PermissionMixIn.get_groups = get_groups |
|
519 |
|
520 @cached |
|
521 def get_rqlexprs(self, action): |
|
522 """return the rql expressions representing queries to check the user is allowed |
|
523 to perform <action> on entities of this type |
|
524 |
|
525 :type action: str |
|
526 :param action: the name of a permission |
|
527 |
|
528 :rtype: tuple |
|
529 :return: the rql expressions with the given permission |
|
530 """ |
|
531 assert action in self.ACTIONS, action |
|
532 #assert action in self._rqlexprs, '%s %s' % (self, action) |
|
533 try: |
|
534 return tuple(g for g in self.permissions[action] if not isinstance(g, basestring)) |
|
535 except KeyError: |
|
536 return () |
|
537 PermissionMixIn.get_rqlexprs = get_rqlexprs |
|
538 |
|
539 orig_set_action_permissions = PermissionMixIn.set_action_permissions |
|
540 def set_action_permissions(self, action, permissions): |
|
541 """set the groups and rql expressions allowing to perform <action> on |
|
542 entities of this type |
|
543 |
|
544 :type action: str |
|
545 :param action: the name of a permission |
|
546 |
|
547 :type permissions: tuple |
|
548 :param permissions: the groups and rql expressions allowing the given action |
|
549 """ |
|
550 orig_set_action_permissions(self, action, tuple(permissions)) |
|
551 clear_cache(self, 'get_rqlexprs') |
|
552 clear_cache(self, 'get_groups') |
|
553 PermissionMixIn.set_action_permissions = set_action_permissions |
|
554 |
|
555 def has_local_role(self, action): |
|
556 """return true if the action *may* be granted localy (eg either rql |
|
557 expressions or the owners group are used in security definition) |
|
558 |
|
559 XXX this method is only there since we don't know well how to deal with |
|
560 'add' action checking. Also find a better name would be nice. |
|
561 """ |
|
562 assert action in self.ACTIONS, action |
|
563 if self.get_rqlexprs(action): |
|
564 return True |
|
565 if action in ('update', 'delete'): |
|
566 return 'owners' in self.get_groups(action) |
|
567 return False |
|
568 PermissionMixIn.has_local_role = has_local_role |
|
569 |
|
570 def may_have_permission(self, action, req): |
|
571 if action != 'read' and not (self.has_local_role('read') or |
|
572 self.has_perm(req, 'read')): |
|
573 return False |
|
574 return self.has_local_role(action) or self.has_perm(req, action) |
|
575 PermissionMixIn.may_have_permission = may_have_permission |
|
576 |
|
577 def has_perm(self, _cw, action, **kwargs): |
|
578 """return true if the action is granted globaly or localy""" |
|
579 try: |
|
580 self.check_perm(_cw, action, **kwargs) |
|
581 return True |
|
582 except Unauthorized: |
|
583 return False |
|
584 PermissionMixIn.has_perm = has_perm |
|
585 |
|
586 |
|
587 def check_perm(self, _cw, action, **kwargs): |
|
588 # NB: _cw may be a server transaction or a request object. |
|
589 # |
|
590 # check user is in an allowed group, if so that's enough internal |
|
591 # transactions should always stop there |
|
592 DBG = False |
|
593 if server.DEBUG & server.DBG_SEC: |
|
594 if action in server._SECURITY_CAPS: |
|
595 _self_str = str(self) |
|
596 if server._SECURITY_ITEMS: |
|
597 if any(item in _self_str for item in server._SECURITY_ITEMS): |
|
598 DBG = True |
|
599 else: |
|
600 DBG = True |
|
601 groups = self.get_groups(action) |
|
602 if _cw.user.matching_groups(groups): |
|
603 if DBG: |
|
604 print 'check_perm: %r %r: user matches %s' % (action, _self_str, groups) |
|
605 return |
|
606 # if 'owners' in allowed groups, check if the user actually owns this |
|
607 # object, if so that's enough |
|
608 # |
|
609 # NB: give _cw to user.owns since user is not be bound to a transaction on |
|
610 # the repository side |
|
611 if 'owners' in groups and ( |
|
612 kwargs.get('creating') |
|
613 or ('eid' in kwargs and _cw.user.owns(kwargs['eid']))): |
|
614 if DBG: |
|
615 print ('check_perm: %r %r: user is owner or creation time' % |
|
616 (action, _self_str)) |
|
617 return |
|
618 # else if there is some rql expressions, check them |
|
619 if DBG: |
|
620 print ('check_perm: %r %r %s' % |
|
621 (action, _self_str, [(rqlexpr, kwargs, rqlexpr.check(_cw, **kwargs)) |
|
622 for rqlexpr in self.get_rqlexprs(action)])) |
|
623 if any(rqlexpr.check(_cw, **kwargs) |
|
624 for rqlexpr in self.get_rqlexprs(action)): |
|
625 return |
|
626 raise Unauthorized(action, str(self)) |
|
627 PermissionMixIn.check_perm = check_perm |
|
628 |
|
629 |
|
630 RelationDefinitionSchema._RPROPERTIES['eid'] = None |
|
631 # remember rproperties defined at this point. Others will have to be serialized in |
|
632 # CWAttribute.extra_props |
|
633 KNOWN_RPROPERTIES = RelationDefinitionSchema.ALL_PROPERTIES() |
|
634 |
|
635 def rql_expression(self, expression, mainvars=None, eid=None): |
|
636 """rql expression factory""" |
|
637 if self.rtype.final: |
|
638 return ERQLExpression(expression, mainvars, eid) |
|
639 return RRQLExpression(expression, mainvars, eid) |
|
640 RelationDefinitionSchema.rql_expression = rql_expression |
|
641 |
|
642 orig_check_permission_definitions = RelationDefinitionSchema.check_permission_definitions |
|
643 def check_permission_definitions(self): |
|
644 orig_check_permission_definitions(self) |
|
645 schema = self.subject.schema |
|
646 for action, groups in self.permissions.iteritems(): |
|
647 for group_or_rqlexpr in groups: |
|
648 if action == 'read' and \ |
|
649 isinstance(group_or_rqlexpr, RQLExpression): |
|
650 msg = "can't use rql expression for read permission of %s" |
|
651 raise BadSchemaDefinition(msg % self) |
|
652 if self.final and isinstance(group_or_rqlexpr, RRQLExpression): |
|
653 msg = "can't use RRQLExpression on %s, use an ERQLExpression" |
|
654 raise BadSchemaDefinition(msg % self) |
|
655 if not self.final and isinstance(group_or_rqlexpr, ERQLExpression): |
|
656 msg = "can't use ERQLExpression on %s, use a RRQLExpression" |
|
657 raise BadSchemaDefinition(msg % self) |
|
658 RelationDefinitionSchema.check_permission_definitions = check_permission_definitions |
|
659 |
|
660 |
|
661 class CubicWebEntitySchema(EntitySchema): |
|
662 """a entity has a type, a set of subject and or object relations |
|
663 the entity schema defines the possible relations for a given type and some |
|
664 constraints on those relations |
|
665 """ |
|
666 def __init__(self, schema=None, edef=None, eid=None, **kwargs): |
|
667 super(CubicWebEntitySchema, self).__init__(schema, edef, **kwargs) |
|
668 if eid is None and edef is not None: |
|
669 eid = getattr(edef, 'eid', None) |
|
670 self.eid = eid |
|
671 |
|
672 def check_permission_definitions(self): |
|
673 super(CubicWebEntitySchema, self).check_permission_definitions() |
|
674 for groups in self.permissions.itervalues(): |
|
675 for group_or_rqlexpr in groups: |
|
676 if isinstance(group_or_rqlexpr, RRQLExpression): |
|
677 msg = "can't use RRQLExpression on %s, use an ERQLExpression" |
|
678 raise BadSchemaDefinition(msg % self.type) |
|
679 |
|
680 def is_subobject(self, strict=False, skiprels=None): |
|
681 if skiprels is None: |
|
682 skiprels = SKIP_COMPOSITE_RELS |
|
683 else: |
|
684 skiprels += SKIP_COMPOSITE_RELS |
|
685 return super(CubicWebEntitySchema, self).is_subobject(strict, |
|
686 skiprels=skiprels) |
|
687 |
|
688 def attribute_definitions(self): |
|
689 """return an iterator on attribute definitions |
|
690 |
|
691 attribute relations are a subset of subject relations where the |
|
692 object's type is a final entity |
|
693 |
|
694 an attribute definition is a 2-uple : |
|
695 * name of the relation |
|
696 * schema of the destination entity type |
|
697 """ |
|
698 iter = super(CubicWebEntitySchema, self).attribute_definitions() |
|
699 for rschema, attrschema in iter: |
|
700 if rschema.type == 'has_text': |
|
701 continue |
|
702 yield rschema, attrschema |
|
703 |
|
704 def main_attribute(self): |
|
705 """convenience method that returns the *main* (i.e. the first non meta) |
|
706 attribute defined in the entity schema |
|
707 """ |
|
708 for rschema, _ in self.attribute_definitions(): |
|
709 if not (rschema in META_RTYPES |
|
710 or self.is_metadata(rschema)): |
|
711 return rschema |
|
712 |
|
713 def add_subject_relation(self, rschema): |
|
714 """register the relation schema as possible subject relation""" |
|
715 super(CubicWebEntitySchema, self).add_subject_relation(rschema) |
|
716 if rschema.final: |
|
717 if self.rdef(rschema).get('fulltextindexed'): |
|
718 self._update_has_text() |
|
719 elif rschema.fulltext_container: |
|
720 self._update_has_text() |
|
721 |
|
722 def add_object_relation(self, rschema): |
|
723 """register the relation schema as possible object relation""" |
|
724 super(CubicWebEntitySchema, self).add_object_relation(rschema) |
|
725 if rschema.fulltext_container: |
|
726 self._update_has_text() |
|
727 |
|
728 def del_subject_relation(self, rtype): |
|
729 super(CubicWebEntitySchema, self).del_subject_relation(rtype) |
|
730 if 'has_text' in self.subjrels: |
|
731 self._update_has_text(deletion=True) |
|
732 |
|
733 def del_object_relation(self, rtype): |
|
734 super(CubicWebEntitySchema, self).del_object_relation(rtype) |
|
735 if 'has_text' in self.subjrels: |
|
736 self._update_has_text(deletion=True) |
|
737 |
|
738 def _update_has_text(self, deletion=False): |
|
739 may_need_has_text, has_has_text = False, False |
|
740 need_has_text = None |
|
741 for rschema in self.subject_relations(): |
|
742 if rschema.final: |
|
743 if rschema == 'has_text': |
|
744 has_has_text = True |
|
745 elif self.rdef(rschema).get('fulltextindexed'): |
|
746 may_need_has_text = True |
|
747 elif rschema.fulltext_container: |
|
748 if rschema.fulltext_container == 'subject': |
|
749 may_need_has_text = True |
|
750 else: |
|
751 need_has_text = False |
|
752 for rschema in self.object_relations(): |
|
753 if rschema.fulltext_container: |
|
754 if rschema.fulltext_container == 'object': |
|
755 may_need_has_text = True |
|
756 else: |
|
757 need_has_text = False |
|
758 if need_has_text is None: |
|
759 need_has_text = may_need_has_text |
|
760 if need_has_text and not has_has_text and not deletion: |
|
761 rdef = ybo.RelationDefinition(self.type, 'has_text', 'String', |
|
762 __permissions__=RO_ATTR_PERMS) |
|
763 self.schema.add_relation_def(rdef) |
|
764 elif not need_has_text and has_has_text: |
|
765 # use rschema.del_relation_def and not schema.del_relation_def to |
|
766 # avoid deleting the relation type accidentally... |
|
767 self.schema['has_text'].del_relation_def(self, self.schema['String']) |
|
768 |
|
769 def schema_entity(self): # XXX @property for consistency with meta |
|
770 """return True if this entity type is used to build the schema""" |
|
771 return self.type in SCHEMA_TYPES |
|
772 |
|
773 def rql_expression(self, expression, mainvars=None, eid=None): |
|
774 """rql expression factory""" |
|
775 return ERQLExpression(expression, mainvars, eid) |
|
776 |
|
777 |
|
778 class CubicWebRelationSchema(RelationSchema): |
|
779 |
|
780 def __init__(self, schema=None, rdef=None, eid=None, **kwargs): |
|
781 if rdef is not None: |
|
782 # if this relation is inlined |
|
783 self.inlined = rdef.inlined |
|
784 super(CubicWebRelationSchema, self).__init__(schema, rdef, **kwargs) |
|
785 if eid is None and rdef is not None: |
|
786 eid = getattr(rdef, 'eid', None) |
|
787 self.eid = eid |
|
788 |
|
789 @property |
|
790 def meta(self): |
|
791 return self.type in META_RTYPES |
|
792 |
|
793 def schema_relation(self): # XXX @property for consistency with meta |
|
794 """return True if this relation type is used to build the schema""" |
|
795 return self.type in SCHEMA_TYPES |
|
796 |
|
797 def may_have_permission(self, action, req, eschema=None, role=None): |
|
798 if eschema is not None: |
|
799 for tschema in self.targets(eschema, role): |
|
800 rdef = self.role_rdef(eschema, tschema, role) |
|
801 if rdef.may_have_permission(action, req): |
|
802 return True |
|
803 else: |
|
804 for rdef in self.rdefs.itervalues(): |
|
805 if rdef.may_have_permission(action, req): |
|
806 return True |
|
807 return False |
|
808 |
|
809 def has_perm(self, _cw, action, **kwargs): |
|
810 """return true if the action is granted globaly or localy""" |
|
811 if self.final: |
|
812 assert not ('fromeid' in kwargs or 'toeid' in kwargs), kwargs |
|
813 assert action in ('read', 'update') |
|
814 if 'eid' in kwargs: |
|
815 subjtype = _cw.describe(kwargs['eid'])[0] |
|
816 else: |
|
817 subjtype = objtype = None |
|
818 else: |
|
819 assert not 'eid' in kwargs, kwargs |
|
820 assert action in ('read', 'add', 'delete') |
|
821 if 'fromeid' in kwargs: |
|
822 subjtype = _cw.describe(kwargs['fromeid'])[0] |
|
823 elif 'frometype' in kwargs: |
|
824 subjtype = kwargs.pop('frometype') |
|
825 else: |
|
826 subjtype = None |
|
827 if 'toeid' in kwargs: |
|
828 objtype = _cw.describe(kwargs['toeid'])[0] |
|
829 elif 'toetype' in kwargs: |
|
830 objtype = kwargs.pop('toetype') |
|
831 else: |
|
832 objtype = None |
|
833 if objtype and subjtype: |
|
834 return self.rdef(subjtype, objtype).has_perm(_cw, action, **kwargs) |
|
835 elif subjtype: |
|
836 for tschema in self.targets(subjtype, 'subject'): |
|
837 rdef = self.rdef(subjtype, tschema) |
|
838 if not rdef.has_perm(_cw, action, **kwargs): |
|
839 return False |
|
840 elif objtype: |
|
841 for tschema in self.targets(objtype, 'object'): |
|
842 rdef = self.rdef(tschema, objtype) |
|
843 if not rdef.has_perm(_cw, action, **kwargs): |
|
844 return False |
|
845 else: |
|
846 for rdef in self.rdefs.itervalues(): |
|
847 if not rdef.has_perm(_cw, action, **kwargs): |
|
848 return False |
|
849 return True |
|
850 |
|
851 @deprecated('use .rdef(subjtype, objtype).role_cardinality(role)') |
|
852 def cardinality(self, subjtype, objtype, target): |
|
853 return self.rdef(subjtype, objtype).role_cardinality(target) |
|
854 |
|
855 |
|
856 class CubicWebSchema(Schema): |
|
857 """set of entities and relations schema defining the possible data sets |
|
858 used in an application |
|
859 |
|
860 :type name: str |
|
861 :ivar name: name of the schema, usually the instance identifier |
|
862 |
|
863 :type base: str |
|
864 :ivar base: path of the directory where the schema is defined |
|
865 """ |
|
866 reading_from_database = False |
|
867 entity_class = CubicWebEntitySchema |
|
868 relation_class = CubicWebRelationSchema |
|
869 no_specialization_inference = ('identity',) |
|
870 |
|
871 def __init__(self, *args, **kwargs): |
|
872 self._eid_index = {} |
|
873 super(CubicWebSchema, self).__init__(*args, **kwargs) |
|
874 ybo.register_base_types(self) |
|
875 rschema = self.add_relation_type(ybo.RelationType('eid')) |
|
876 rschema.final = True |
|
877 rschema = self.add_relation_type(ybo.RelationType('has_text')) |
|
878 rschema.final = True |
|
879 rschema = self.add_relation_type(ybo.RelationType('identity')) |
|
880 rschema.final = False |
|
881 |
|
882 etype_name_re = r'[A-Z][A-Za-z0-9]*[a-z]+[A-Za-z0-9]*$' |
|
883 def add_entity_type(self, edef): |
|
884 edef.name = edef.name.encode() |
|
885 edef.name = bw_normalize_etype(edef.name) |
|
886 if not re.match(self.etype_name_re, edef.name): |
|
887 raise BadSchemaDefinition( |
|
888 '%r is not a valid name for an entity type. It should start ' |
|
889 'with an upper cased letter and be followed by at least a ' |
|
890 'lower cased letter' % edef.name) |
|
891 eschema = super(CubicWebSchema, self).add_entity_type(edef) |
|
892 if not eschema.final: |
|
893 # automatically add the eid relation to non final entity types |
|
894 rdef = ybo.RelationDefinition(eschema.type, 'eid', 'Int', |
|
895 cardinality='11', uid=True, |
|
896 __permissions__=RO_ATTR_PERMS) |
|
897 self.add_relation_def(rdef) |
|
898 rdef = ybo.RelationDefinition(eschema.type, 'identity', eschema.type, |
|
899 __permissions__=RO_REL_PERMS) |
|
900 self.add_relation_def(rdef) |
|
901 self._eid_index[eschema.eid] = eschema |
|
902 return eschema |
|
903 |
|
904 def add_relation_type(self, rdef): |
|
905 if not rdef.name.islower(): |
|
906 raise BadSchemaDefinition( |
|
907 '%r is not a valid name for a relation type. It should be ' |
|
908 'lower cased' % rdef.name) |
|
909 rdef.name = rdef.name.encode() |
|
910 rschema = super(CubicWebSchema, self).add_relation_type(rdef) |
|
911 self._eid_index[rschema.eid] = rschema |
|
912 return rschema |
|
913 |
|
914 def add_relation_def(self, rdef): |
|
915 """build a part of a relation schema |
|
916 (i.e. add a relation between two specific entity's types) |
|
917 |
|
918 :type subject: str |
|
919 :param subject: entity's type that is subject of the relation |
|
920 |
|
921 :type rtype: str |
|
922 :param rtype: the relation's type (i.e. the name of the relation) |
|
923 |
|
924 :type obj: str |
|
925 :param obj: entity's type that is object of the relation |
|
926 |
|
927 :rtype: RelationSchema |
|
928 :param: the newly created or just completed relation schema |
|
929 """ |
|
930 rdef.name = rdef.name.lower() |
|
931 rdef.subject = bw_normalize_etype(rdef.subject) |
|
932 rdef.object = bw_normalize_etype(rdef.object) |
|
933 rdefs = super(CubicWebSchema, self).add_relation_def(rdef) |
|
934 if rdefs: |
|
935 try: |
|
936 self._eid_index[rdef.eid] = rdefs |
|
937 except AttributeError: |
|
938 pass # not a serialized schema |
|
939 return rdefs |
|
940 |
|
941 def del_relation_type(self, rtype): |
|
942 rschema = self.rschema(rtype) |
|
943 self._eid_index.pop(rschema.eid, None) |
|
944 super(CubicWebSchema, self).del_relation_type(rtype) |
|
945 |
|
946 def del_relation_def(self, subjtype, rtype, objtype): |
|
947 for k, v in self._eid_index.items(): |
|
948 if not isinstance(v, RelationDefinitionSchema): |
|
949 continue |
|
950 if v.subject == subjtype and v.rtype == rtype and v.object == objtype: |
|
951 del self._eid_index[k] |
|
952 break |
|
953 super(CubicWebSchema, self).del_relation_def(subjtype, rtype, objtype) |
|
954 |
|
955 def del_entity_type(self, etype): |
|
956 eschema = self.eschema(etype) |
|
957 self._eid_index.pop(eschema.eid, None) |
|
958 # deal with has_text first, else its automatic deletion (see above) |
|
959 # may trigger an error in ancestor's del_entity_type method |
|
960 if 'has_text' in eschema.subject_relations(): |
|
961 self.del_relation_def(etype, 'has_text', 'String') |
|
962 super(CubicWebSchema, self).del_entity_type(etype) |
|
963 |
|
964 def schema_by_eid(self, eid): |
|
965 return self._eid_index[eid] |
|
966 |
928 |
967 |
929 # additional cw specific constraints ########################################### |
968 # additional cw specific constraints ########################################### |
930 |
969 |
931 class BaseRQLConstraint(RRQLExpression, BaseConstraint): |
970 class BaseRQLConstraint(RRQLExpression, BaseConstraint): |
932 """base class for rql constraints""" |
971 """base class for rql constraints""" |
933 distinct_query = None |
972 distinct_query = None |
934 |
973 |
935 def serialize(self): |
974 def serialize(self): |
936 # start with a comma for bw compat,see below |
975 # start with a semicolon for bw compat, see below |
937 return ';' + ','.join(sorted(self.mainvars)) + ';' + self.expression |
976 return ';' + ','.join(sorted(self.mainvars)) + ';' + self.expression |
938 |
977 |
939 @classmethod |
978 @classmethod |
940 def deserialize(cls, value): |
979 def deserialize(cls, value): |
941 # XXX < 3.5.10 bw compat |
|
942 if not value.startswith(';'): |
|
943 return cls(value) |
|
944 _, mainvars, expression = value.split(';', 2) |
980 _, mainvars, expression = value.split(';', 2) |
945 return cls(expression, mainvars) |
981 return cls(expression, mainvars) |
946 |
982 |
947 def check(self, entity, rtype, value): |
983 def check(self, entity, rtype, value): |
948 """return true if the value satisfy the constraint, else false""" |
984 """return true if the value satisfy the constraint, else false""" |