121 # with some cases of entity classes inheritance. |
122 # with some cases of entity classes inheritance. |
122 mixins.insert(0, cls.__bases__[0]) |
123 mixins.insert(0, cls.__bases__[0]) |
123 mixins += cls.__bases__[1:] |
124 mixins += cls.__bases__[1:] |
124 cls.__bases__ = tuple(mixins) |
125 cls.__bases__ = tuple(mixins) |
125 cls.info('plugged %s mixins on %s', mixins, cls) |
126 cls.info('plugged %s mixins on %s', mixins, cls) |
|
127 |
|
128 fetch_attrs = ('modification_date',) |
|
129 @classmethod |
|
130 def fetch_order(cls, attr, var): |
|
131 """class method used to control sort order when multiple entities of |
|
132 this type are fetched |
|
133 """ |
|
134 return cls.fetch_unrelated_order(attr, var) |
|
135 |
|
136 @classmethod |
|
137 def fetch_unrelated_order(cls, attr, var): |
|
138 """class method used to control sort order when multiple entities of |
|
139 this type are fetched to use in edition (eg propose them to create a |
|
140 new relation on an edited entity). |
|
141 """ |
|
142 if attr == 'modification_date': |
|
143 return '%s DESC' % var |
|
144 return None |
126 |
145 |
127 @classmethod |
146 @classmethod |
128 def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X', |
147 def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X', |
129 settype=True, ordermethod='fetch_order'): |
148 settype=True, ordermethod='fetch_order'): |
130 """return a rql to fetch all entities of the class type""" |
149 """return a rql to fetch all entities of the class type""" |
343 elif not has_load_left and edited: |
368 elif not has_load_left and edited: |
344 # cleanup, this may cause undesired changes |
369 # cleanup, this may cause undesired changes |
345 del self.entity['load_left'] |
370 del self.entity['load_left'] |
346 |
371 |
347 """ |
372 """ |
348 super(Entity, self).__delitem__(attr) |
373 del self.cw_attr_cache[attr] |
349 if hasattr(self, 'edited_attributes'): |
374 if hasattr(self, 'edited_attributes'): |
350 self.edited_attributes.remove(attr) |
375 self.edited_attributes.remove(attr) |
351 |
376 |
|
377 def clear(self): |
|
378 self.cw_attr_cache.clear() |
|
379 |
|
380 def get(self, key, default=None): |
|
381 return self.cw_attr_cache.get(key, default) |
|
382 |
352 def setdefault(self, attr, default): |
383 def setdefault(self, attr, default): |
353 """override setdefault to update self.edited_attributes""" |
384 """override setdefault to update self.edited_attributes""" |
354 super(Entity, self).setdefault(attr, default) |
385 value = self.cw_attr_cache.setdefault(attr, default) |
355 # don't add attribute into skip_security if already in edited |
386 # don't add attribute into skip_security if already in edited |
356 # attributes, else we may accidentaly skip a desired security check |
387 # attributes, else we may accidentaly skip a desired security check |
357 if hasattr(self, 'edited_attributes') and \ |
388 if hasattr(self, 'edited_attributes') and \ |
358 attr not in self.edited_attributes: |
389 attr not in self.edited_attributes: |
359 self.edited_attributes.add(attr) |
390 self.edited_attributes.add(attr) |
360 self.skip_security_attributes.add(attr) |
391 self._cw_skip_security_attributes.add(attr) |
|
392 return value |
361 |
393 |
362 def pop(self, attr, default=_marker): |
394 def pop(self, attr, default=_marker): |
363 """override pop to update self.edited_attributes on cleanup of |
395 """override pop to update self.edited_attributes on cleanup of |
364 undesired changes introduced in the entity's dict. See `__delitem__` |
396 undesired changes introduced in the entity's dict. See `__delitem__` |
365 """ |
397 """ |
366 if default is _marker: |
398 if default is _marker: |
367 value = super(Entity, self).pop(attr) |
399 value = self.cw_attr_cache.pop(attr) |
368 else: |
400 else: |
369 value = super(Entity, self).pop(attr, default) |
401 value = self.cw_attr_cache.pop(attr, default) |
370 if hasattr(self, 'edited_attributes') and attr in self.edited_attributes: |
402 if hasattr(self, 'edited_attributes') and attr in self.edited_attributes: |
371 self.edited_attributes.remove(attr) |
403 self.edited_attributes.remove(attr) |
372 return value |
404 return value |
373 |
405 |
374 def update(self, values): |
406 def update(self, values): |
375 """override update to update self.edited_attributes. See `__setitem__` |
407 """override update to update self.edited_attributes. See `__setitem__` |
376 """ |
408 """ |
377 for attr, value in values.items(): |
409 for attr, value in values.items(): |
378 self[attr] = value # use self.__setitem__ implementation |
410 self[attr] = value # use self.__setitem__ implementation |
379 |
411 |
380 def rql_set_value(self, attr, value): |
412 def cw_adapt_to(self, interface): |
381 """call by rql execution plan when some attribute is modified |
413 """return an adapter the entity to the given interface name. |
382 |
414 |
383 don't use dict api in such case since we don't want attribute to be |
415 return None if it can not be adapted. |
384 added to skip_security_attributes. |
416 """ |
385 """ |
417 try: |
386 super(Entity, self).__setitem__(attr, value) |
418 cache = self._cw_adapters_cache |
387 |
419 except AttributeError: |
388 def pre_add_hook(self): |
420 self._cw_adapters_cache = cache = {} |
389 """hook called by the repository before doing anything to add the entity |
421 try: |
390 (before_add entity hooks have not been called yet). This give the |
422 return cache[interface] |
391 occasion to do weird stuff such as autocast (File -> Image for instance). |
423 except KeyError: |
392 |
424 adapter = self._cw.vreg['adapters'].select_or_none( |
393 This method must return the actual entity to be added. |
425 interface, self._cw, entity=self) |
394 """ |
426 cache[interface] = adapter |
395 return self |
427 return adapter |
396 |
428 |
397 def set_eid(self, eid): |
429 def has_eid(self): # XXX cw_has_eid |
398 self.eid = eid |
|
399 |
|
400 def has_eid(self): |
|
401 """return True if the entity has an attributed eid (False |
430 """return True if the entity has an attributed eid (False |
402 meaning that the entity has to be created |
431 meaning that the entity has to be created |
403 """ |
432 """ |
404 try: |
433 try: |
405 typed_eid(self.eid) |
434 typed_eid(self.eid) |
406 return True |
435 return True |
407 except (ValueError, TypeError): |
436 except (ValueError, TypeError): |
408 return False |
437 return False |
409 |
438 |
410 def is_saved(self): |
439 def cw_is_saved(self): |
411 """during entity creation, there is some time during which the entity |
440 """during entity creation, there is some time during which the entity |
412 has an eid attributed though it's not saved (eg during before_add_entity |
441 has an eid attributed though it's not saved (eg during |
413 hooks). You can use this method to ensure the entity has an eid *and* is |
442 'before_add_entity' hooks). You can use this method to ensure the entity |
414 saved in its source. |
443 has an eid *and* is saved in its source. |
415 """ |
444 """ |
416 return self.has_eid() and self._is_saved |
445 return self.has_eid() and self._cw_is_saved |
417 |
446 |
418 @cached |
447 @cached |
419 def metainformation(self): |
448 def cw_metainformation(self): |
420 res = dict(zip(('type', 'source', 'extid'), self._cw.describe(self.eid))) |
449 res = dict(zip(('type', 'source', 'extid'), self._cw.describe(self.eid))) |
421 res['source'] = self._cw.source_defs()[res['source']] |
450 res['source'] = self._cw.source_defs()[res['source']] |
422 return res |
451 return res |
423 |
452 |
424 def clear_local_perm_cache(self, action): |
453 def cw_check_perm(self, action): |
425 for rqlexpr in self.e_schema.get_rqlexprs(action): |
|
426 self._cw.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None) |
|
427 |
|
428 def check_perm(self, action): |
|
429 self.e_schema.check_perm(self._cw, action, eid=self.eid) |
454 self.e_schema.check_perm(self._cw, action, eid=self.eid) |
430 |
455 |
431 def has_perm(self, action): |
456 def cw_has_perm(self, action): |
432 return self.e_schema.has_perm(self._cw, action, eid=self.eid) |
457 return self.e_schema.has_perm(self._cw, action, eid=self.eid) |
433 |
458 |
434 def view(self, __vid, __registry='views', w=None, **kwargs): |
459 def view(self, __vid, __registry='views', w=None, **kwargs): # XXX cw_view |
435 """shortcut to apply a view on this entity""" |
460 """shortcut to apply a view on this entity""" |
436 view = self._cw.vreg[__registry].select(__vid, self._cw, rset=self.cw_rset, |
461 view = self._cw.vreg[__registry].select(__vid, self._cw, rset=self.cw_rset, |
437 row=self.cw_row, col=self.cw_col, |
462 row=self.cw_row, col=self.cw_col, |
438 **kwargs) |
463 **kwargs) |
439 return view.render(row=self.cw_row, col=self.cw_col, w=w, **kwargs) |
464 return view.render(row=self.cw_row, col=self.cw_col, w=w, **kwargs) |
440 |
465 |
441 def absolute_url(self, *args, **kwargs): |
466 def absolute_url(self, *args, **kwargs): # XXX cw_url |
442 """return an absolute url to view this entity""" |
467 """return an absolute url to view this entity""" |
443 # use *args since we don't want first argument to be "anonymous" to |
468 # use *args since we don't want first argument to be "anonymous" to |
444 # avoid potential clash with kwargs |
469 # avoid potential clash with kwargs |
445 if args: |
470 if args: |
446 assert len(args) == 1, 'only 0 or 1 non-named-argument expected' |
471 assert len(args) == 1, 'only 0 or 1 non-named-argument expected' |
516 if attrtype == 'String': |
541 if attrtype == 'String': |
517 # internalinalized *and* formatted string such as schema |
542 # internalinalized *and* formatted string such as schema |
518 # description... |
543 # description... |
519 if props.internationalizable: |
544 if props.internationalizable: |
520 value = self._cw._(value) |
545 value = self._cw._(value) |
521 attrformat = self.attr_metadata(attr, 'format') |
546 attrformat = self.cw_attr_metadata(attr, 'format') |
522 if attrformat: |
547 if attrformat: |
523 return self.mtc_transform(value, attrformat, format, |
548 return self._cw_mtc_transform(value, attrformat, format, |
524 self._cw.encoding) |
549 self._cw.encoding) |
525 elif attrtype == 'Bytes': |
550 elif attrtype == 'Bytes': |
526 attrformat = self.attr_metadata(attr, 'format') |
551 attrformat = self.cw_attr_metadata(attr, 'format') |
527 if attrformat: |
552 if attrformat: |
528 encoding = self.attr_metadata(attr, 'encoding') |
553 encoding = self.cw_attr_metadata(attr, 'encoding') |
529 return self.mtc_transform(value.getvalue(), attrformat, format, |
554 return self._cw_mtc_transform(value.getvalue(), attrformat, format, |
530 encoding) |
555 encoding) |
531 return u'' |
556 return u'' |
532 value = printable_value(self._cw, attrtype, value, props, |
557 value = printable_value(self._cw, attrtype, value, props, |
533 displaytime=displaytime) |
558 displaytime=displaytime) |
534 if format == 'text/html': |
559 if format == 'text/html': |
535 value = xml_escape(value) |
560 value = xml_escape(value) |
536 return value |
561 return value |
537 |
562 |
538 def mtc_transform(self, data, format, target_format, encoding, |
563 def _cw_mtc_transform(self, data, format, target_format, encoding, |
539 _engine=ENGINE): |
564 _engine=ENGINE): |
540 trdata = TransformData(data, format, encoding, appobject=self) |
565 trdata = TransformData(data, format, encoding, appobject=self) |
541 data = _engine.convert(trdata, target_format).decode() |
566 data = _engine.convert(trdata, target_format).decode() |
542 if format == 'text/html': |
567 if format == 'text/html': |
543 data = soup2xhtml(data, self._cw.encoding) |
568 data = soup2xhtml(data, self._cw.encoding) |
544 return data |
569 return data |
545 |
570 |
546 # entity cloning ########################################################## |
571 # entity cloning ########################################################## |
547 |
572 |
548 def copy_relations(self, ceid): |
573 def cw_copy(self): |
|
574 thecopy = copy(self) |
|
575 thecopy.cw_attr_cache = copy(self.cw_attr_cache) |
|
576 thecopy._cw_related_cache = {} |
|
577 return thecopy |
|
578 |
|
579 def copy_relations(self, ceid): # XXX cw_copy_relations |
549 """copy relations of the object with the given eid on this |
580 """copy relations of the object with the given eid on this |
550 object (this method is called on the newly created copy, and |
581 object (this method is called on the newly created copy, and |
551 ceid designates the original entity). |
582 ceid designates the original entity). |
552 |
583 |
553 By default meta and composite relations are skipped. |
584 By default meta and composite relations are skipped. |
590 if rdef.cardinality[0] in '?1': |
621 if rdef.cardinality[0] in '?1': |
591 continue |
622 continue |
592 rql = 'SET V %s X WHERE X eid %%(x)s, Y eid %%(y)s, V %s Y' % ( |
623 rql = 'SET V %s X WHERE X eid %%(x)s, Y eid %%(y)s, V %s Y' % ( |
593 rschema.type, rschema.type) |
624 rschema.type, rschema.type) |
594 execute(rql, {'x': self.eid, 'y': ceid}) |
625 execute(rql, {'x': self.eid, 'y': ceid}) |
595 self.clear_related_cache(rschema.type, 'object') |
626 self.cw_clear_relation_cache(rschema.type, 'object') |
596 |
627 |
597 # data fetching methods ################################################### |
628 # data fetching methods ################################################### |
598 |
629 |
599 @cached |
630 @cached |
600 def as_rset(self): |
631 def as_rset(self): # XXX .cw_as_rset |
601 """returns a resultset containing `self` information""" |
632 """returns a resultset containing `self` information""" |
602 rset = ResultSet([(self.eid,)], 'Any X WHERE X eid %(x)s', |
633 rset = ResultSet([(self.eid,)], 'Any X WHERE X eid %(x)s', |
603 {'x': self.eid}, [(self.__regid__,)]) |
634 {'x': self.eid}, [(self.__regid__,)]) |
604 rset.req = self._cw |
635 rset.req = self._cw |
605 return rset |
636 return rset |
606 |
637 |
607 def to_complete_relations(self): |
638 def _cw_to_complete_relations(self): |
608 """by default complete final relations to when calling .complete()""" |
639 """by default complete final relations to when calling .complete()""" |
609 for rschema in self.e_schema.subject_relations(): |
640 for rschema in self.e_schema.subject_relations(): |
610 if rschema.final: |
641 if rschema.final: |
611 continue |
642 continue |
612 targets = rschema.objects(self.e_schema) |
643 targets = rschema.objects(self.e_schema) |
613 if len(targets) > 1: |
|
614 # ambigous relations, the querier doesn't handle |
|
615 # outer join correctly in this case |
|
616 continue |
|
617 if rschema.inlined: |
644 if rschema.inlined: |
618 matching_groups = self._cw.user.matching_groups |
645 matching_groups = self._cw.user.matching_groups |
619 rdef = rschema.rdef(self.e_schema, targets[0]) |
646 if all(matching_groups(e.get_groups('read')) and |
620 if matching_groups(rdef.get_groups('read')) and \ |
647 rschema.rdef(self.e_schema, e).get_groups('read') |
621 all(matching_groups(e.get_groups('read')) for e in targets): |
648 for e in targets): |
622 yield rschema, 'subject' |
649 yield rschema, 'subject' |
623 |
650 |
624 def to_complete_attributes(self, skip_bytes=True, skip_pwd=True): |
651 def _cw_to_complete_attributes(self, skip_bytes=True, skip_pwd=True): |
625 for rschema, attrschema in self.e_schema.attribute_definitions(): |
652 for rschema, attrschema in self.e_schema.attribute_definitions(): |
626 # skip binary data by default |
653 # skip binary data by default |
627 if skip_bytes and attrschema.type == 'Bytes': |
654 if skip_bytes and attrschema.type == 'Bytes': |
628 continue |
655 continue |
629 attr = rschema.type |
656 attr = rschema.type |
653 self._cw_completed = True |
680 self._cw_completed = True |
654 varmaker = rqlvar_maker() |
681 varmaker = rqlvar_maker() |
655 V = varmaker.next() |
682 V = varmaker.next() |
656 rql = ['WHERE %s eid %%(x)s' % V] |
683 rql = ['WHERE %s eid %%(x)s' % V] |
657 selected = [] |
684 selected = [] |
658 for attr in (attributes or self.to_complete_attributes(skip_bytes, skip_pwd)): |
685 for attr in (attributes or self._cw_to_complete_attributes(skip_bytes, skip_pwd)): |
659 # if attribute already in entity, nothing to do |
686 # if attribute already in entity, nothing to do |
660 if self.has_key(attr): |
687 if self.cw_attr_cache.has_key(attr): |
661 continue |
688 continue |
662 # case where attribute must be completed, but is not yet in entity |
689 # case where attribute must be completed, but is not yet in entity |
663 var = varmaker.next() |
690 var = varmaker.next() |
664 rql.append('%s %s %s' % (V, attr, var)) |
691 rql.append('%s %s %s' % (V, attr, var)) |
665 selected.append((attr, var)) |
692 selected.append((attr, var)) |
666 # +1 since this doen't include the main variable |
693 # +1 since this doen't include the main variable |
667 lastattr = len(selected) + 1 |
694 lastattr = len(selected) + 1 |
668 if attributes is None: |
695 if attributes is None: |
669 # fetch additional relations (restricted to 0..1 relations) |
696 # fetch additional relations (restricted to 0..1 relations) |
670 for rschema, role in self.to_complete_relations(): |
697 for rschema, role in self._cw_to_complete_relations(): |
671 rtype = rschema.type |
698 rtype = rschema.type |
672 if self.relation_cached(rtype, role): |
699 if self.cw_relation_cached(rtype, role): |
673 continue |
700 continue |
|
701 # at this point we suppose that: |
|
702 # * this is a inlined relation |
|
703 # * entity (self) is the subject |
|
704 # * user has read perm on the relation and on the target entity |
|
705 assert rschema.inlined |
|
706 assert role == 'subject' |
674 var = varmaker.next() |
707 var = varmaker.next() |
675 targettype = rschema.targets(self.e_schema, role)[0] |
708 # keep outer join anyway, we don't want .complete to crash on |
676 rdef = rschema.role_rdef(self.e_schema, targettype, role) |
709 # missing mandatory relation (see #1058267) |
677 card = rdef.role_cardinality(role) |
710 rql.append('%s %s %s?' % (V, rtype, var)) |
678 assert card in '1?', '%s %s %s %s' % (self.e_schema, rtype, |
|
679 role, card) |
|
680 if role == 'subject': |
|
681 if card == '1': |
|
682 rql.append('%s %s %s' % (V, rtype, var)) |
|
683 else: |
|
684 rql.append('%s %s %s?' % (V, rtype, var)) |
|
685 else: |
|
686 if card == '1': |
|
687 rql.append('%s %s %s' % (var, rtype, V)) |
|
688 else: |
|
689 rql.append('%s? %s %s' % (var, rtype, V)) |
|
690 selected.append(((rtype, role), var)) |
711 selected.append(((rtype, role), var)) |
691 if selected: |
712 if selected: |
692 # select V, we need it as the left most selected variable |
713 # select V, we need it as the left most selected variable |
693 # if some outer join are included to fetch inlined relations |
714 # if some outer join are included to fetch inlined relations |
694 rql = 'Any %s,%s %s' % (V, ','.join(var for attr, var in selected), |
715 rql = 'Any %s,%s %s' % (V, ','.join(var for attr, var in selected), |
738 self[name] = value = self._cw._('unaccessible') |
759 self[name] = value = self._cw._('unaccessible') |
739 else: |
760 else: |
740 self[name] = value = None |
761 self[name] = value = None |
741 return value |
762 return value |
742 |
763 |
743 def related(self, rtype, role='subject', limit=None, entities=False): |
764 def related(self, rtype, role='subject', limit=None, entities=False): # XXX .cw_related |
744 """returns a resultset of related entities |
765 """returns a resultset of related entities |
745 |
766 |
746 :param role: is the role played by 'self' in the relation ('subject' or 'object') |
767 :param role: is the role played by 'self' in the relation ('subject' or 'object') |
747 :param limit: resultset's maximum size |
768 :param limit: resultset's maximum size |
748 :param entities: if True, the entites are returned; if False, a result set is returned |
769 :param entities: if True, the entites are returned; if False, a result set is returned |
749 """ |
770 """ |
750 try: |
771 try: |
751 return self.related_cache(rtype, role, entities, limit) |
772 return self._cw_relation_cache(rtype, role, entities, limit) |
752 except KeyError: |
773 except KeyError: |
753 pass |
774 pass |
754 if not self.has_eid(): |
775 if not self.has_eid(): |
755 if entities: |
776 if entities: |
756 return [] |
777 return [] |
757 return self.empty_rset() |
778 return self.empty_rset() |
758 rql = self.related_rql(rtype, role) |
779 rql = self.cw_related_rql(rtype, role) |
759 rset = self._cw.execute(rql, {'x': self.eid}) |
780 rset = self._cw.execute(rql, {'x': self.eid}) |
760 self.set_related_cache(rtype, role, rset) |
781 self.cw_set_relation_cache(rtype, role, rset) |
761 return self.related(rtype, role, limit, entities) |
782 return self.related(rtype, role, limit, entities) |
762 |
783 |
763 def related_rql(self, rtype, role='subject', targettypes=None): |
784 def cw_related_rql(self, rtype, role='subject', targettypes=None): |
764 rschema = self._cw.vreg.schema[rtype] |
785 rschema = self._cw.vreg.schema[rtype] |
765 if role == 'subject': |
786 if role == 'subject': |
766 restriction = 'E eid %%(x)s, E %s X' % rtype |
787 restriction = 'E eid %%(x)s, E %s X' % rtype |
767 if targettypes is None: |
788 if targettypes is None: |
768 targettypes = rschema.objects(self.e_schema) |
789 targettypes = rschema.objects(self.e_schema) |
869 select.solutions, args, existant) |
890 select.solutions, args, existant) |
870 rql = rqlst.as_string() |
891 rql = rqlst.as_string() |
871 return rql, args |
892 return rql, args |
872 |
893 |
873 def unrelated(self, rtype, targettype, role='subject', limit=None, |
894 def unrelated(self, rtype, targettype, role='subject', limit=None, |
874 ordermethod=None): |
895 ordermethod=None): # XXX .cw_unrelated |
875 """return a result set of target type objects that may be related |
896 """return a result set of target type objects that may be related |
876 by a given relation, with self as subject or object |
897 by a given relation, with self as subject or object |
877 """ |
898 """ |
878 try: |
899 try: |
879 rql, args = self.unrelated_rql(rtype, targettype, role, ordermethod) |
900 rql, args = self.cw_unrelated_rql(rtype, targettype, role, ordermethod) |
880 except Unauthorized: |
901 except Unauthorized: |
881 return self._cw.empty_rset() |
902 return self._cw.empty_rset() |
882 if limit is not None: |
903 if limit is not None: |
883 before, after = rql.split(' WHERE ', 1) |
904 before, after = rql.split(' WHERE ', 1) |
884 rql = '%s LIMIT %s WHERE %s' % (before, limit, after) |
905 rql = '%s LIMIT %s WHERE %s' % (before, limit, after) |
885 return self._cw.execute(rql, args) |
906 return self._cw.execute(rql, args) |
886 |
907 |
887 # relations cache handling ################################################ |
908 # relations cache handling ################################################# |
888 |
909 |
889 def relation_cached(self, rtype, role): |
910 def cw_relation_cached(self, rtype, role): |
890 """return true if the given relation is already cached on the instance |
911 """return None if the given relation isn't already cached on the |
891 """ |
912 instance, else the content of the cache (a 2-uple (rset, entities)). |
892 return self._related_cache.get('%s_%s' % (rtype, role)) |
913 """ |
893 |
914 return self._cw_related_cache.get('%s_%s' % (rtype, role)) |
894 def related_cache(self, rtype, role, entities=True, limit=None): |
915 |
|
916 def _cw_relation_cache(self, rtype, role, entities=True, limit=None): |
895 """return values for the given relation if it's cached on the instance, |
917 """return values for the given relation if it's cached on the instance, |
896 else raise `KeyError` |
918 else raise `KeyError` |
897 """ |
919 """ |
898 res = self._related_cache['%s_%s' % (rtype, role)][entities] |
920 res = self._cw_related_cache['%s_%s' % (rtype, role)][entities] |
899 if limit is not None and limit < len(res): |
921 if limit is not None and limit < len(res): |
900 if entities: |
922 if entities: |
901 res = res[:limit] |
923 res = res[:limit] |
902 else: |
924 else: |
903 res = res.limit(limit) |
925 res = res.limit(limit) |
904 return res |
926 return res |
905 |
927 |
906 def set_related_cache(self, rtype, role, rset, col=0): |
928 def cw_set_relation_cache(self, rtype, role, rset): |
907 """set cached values for the given relation""" |
929 """set cached values for the given relation""" |
908 if rset: |
930 if rset: |
909 related = list(rset.entities(col)) |
931 related = list(rset.entities(0)) |
910 rschema = self._cw.vreg.schema.rschema(rtype) |
932 rschema = self._cw.vreg.schema.rschema(rtype) |
911 if role == 'subject': |
933 if role == 'subject': |
912 rcard = rschema.rdef(self.e_schema, related[0].e_schema).cardinality[1] |
934 rcard = rschema.rdef(self.e_schema, related[0].e_schema).cardinality[1] |
913 target = 'object' |
935 target = 'object' |
914 else: |
936 else: |
915 rcard = rschema.rdef(related[0].e_schema, self.e_schema).cardinality[0] |
937 rcard = rschema.rdef(related[0].e_schema, self.e_schema).cardinality[0] |
916 target = 'subject' |
938 target = 'subject' |
917 if rcard in '?1': |
939 if rcard in '?1': |
918 for rentity in related: |
940 for rentity in related: |
919 rentity._related_cache['%s_%s' % (rtype, target)] = ( |
941 rentity._cw_related_cache['%s_%s' % (rtype, target)] = ( |
920 self.as_rset(), (self,)) |
942 self.as_rset(), (self,)) |
921 else: |
943 else: |
922 related = () |
944 related = () |
923 self._related_cache['%s_%s' % (rtype, role)] = (rset, related) |
945 self._cw_related_cache['%s_%s' % (rtype, role)] = (rset, related) |
924 |
946 |
925 def clear_related_cache(self, rtype=None, role=None): |
947 def cw_clear_relation_cache(self, rtype=None, role=None): |
926 """clear cached values for the given relation or the entire cache if |
948 """clear cached values for the given relation or the entire cache if |
927 no relation is given |
949 no relation is given |
928 """ |
950 """ |
929 if rtype is None: |
951 if rtype is None: |
930 self._related_cache = {} |
952 self._cw_related_cache = {} |
|
953 self._cw_adapters_cache = {} |
931 else: |
954 else: |
932 assert role |
955 assert role |
933 self._related_cache.pop('%s_%s' % (rtype, role), None) |
956 self._cw_related_cache.pop('%s_%s' % (rtype, role), None) |
934 |
957 |
935 def clear_all_caches(self): |
958 def clear_all_caches(self): # XXX cw_clear_all_caches |
936 """flush all caches on this entity. Further attributes/relations access |
959 """flush all caches on this entity. Further attributes/relations access |
937 will triggers new database queries to get back values. |
960 will triggers new database queries to get back values. |
938 |
961 |
939 If you use custom caches on your entity class (take care to @cached!), |
962 If you use custom caches on your entity class (take care to @cached!), |
940 you should override this method to clear them as well. |
963 you should override this method to clear them as well. |
941 """ |
964 """ |
942 # clear attributes cache |
965 # clear attributes cache |
943 haseid = 'eid' in self |
966 haseid = 'eid' in self |
944 self._cw_completed = False |
967 self._cw_completed = False |
945 self.clear() |
968 self.cw_attr_cache.clear() |
946 # clear relations cache |
969 # clear relations cache |
947 for rschema, _, role in self.e_schema.relation_definitions(): |
970 self.cw_clear_relation_cache() |
948 self.clear_related_cache(rschema.type, role) |
|
949 # rest path unique cache |
971 # rest path unique cache |
950 try: |
972 try: |
951 del self.__unique |
973 del self.__unique |
952 except AttributeError: |
974 except AttributeError: |
953 pass |
975 pass |
954 |
976 |
955 # raw edition utilities ################################################### |
977 # raw edition utilities ################################################### |
956 |
978 |
957 def set_attributes(self, **kwargs): |
979 def set_attributes(self, **kwargs): # XXX cw_set_attributes |
958 _check_cw_unsafe(kwargs) |
980 _check_cw_unsafe(kwargs) |
959 assert kwargs |
981 assert kwargs |
960 assert self._is_saved, "should not call set_attributes while entity "\ |
982 assert self.cw_is_saved(), "should not call set_attributes while entity "\ |
961 "hasn't been saved yet" |
983 "hasn't been saved yet" |
962 relations = [] |
984 relations = [] |
963 for key in kwargs: |
985 for key in kwargs: |
964 relations.append('X %s %%(%s)s' % (key, key)) |
986 relations.append('X %s %%(%s)s' % (key, key)) |
965 # and now update the database |
987 # and now update the database |
994 values = (values,) |
1016 values = (values,) |
995 self._cw.execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % ( |
1017 self._cw.execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % ( |
996 restr, ','.join(str(r.eid) for r in values)), |
1018 restr, ','.join(str(r.eid) for r in values)), |
997 {'x': self.eid}) |
1019 {'x': self.eid}) |
998 |
1020 |
999 def delete(self, **kwargs): |
1021 def cw_delete(self, **kwargs): |
1000 assert self.has_eid(), self.eid |
1022 assert self.has_eid(), self.eid |
1001 self._cw.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema, |
1023 self._cw.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema, |
1002 {'x': self.eid}, **kwargs) |
1024 {'x': self.eid}, **kwargs) |
1003 |
1025 |
1004 # server side utilities ################################################### |
1026 # server side utilities ################################################### |
1005 |
1027 |
|
1028 def _cw_rql_set_value(self, attr, value): |
|
1029 """call by rql execution plan when some attribute is modified |
|
1030 |
|
1031 don't use dict api in such case since we don't want attribute to be |
|
1032 added to skip_security_attributes. |
|
1033 |
|
1034 This method is for internal use, you should not use it. |
|
1035 """ |
|
1036 self.cw_attr_cache[attr] = value |
|
1037 |
|
1038 def _cw_clear_local_perm_cache(self, action): |
|
1039 for rqlexpr in self.e_schema.get_rqlexprs(action): |
|
1040 self._cw.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None) |
|
1041 |
1006 @property |
1042 @property |
1007 def skip_security_attributes(self): |
1043 def _cw_skip_security_attributes(self): |
1008 try: |
1044 try: |
1009 return self._skip_security_attributes |
1045 return self.__cw_skip_security_attributes |
1010 except: |
1046 except: |
1011 self._skip_security_attributes = set() |
1047 self.__cw_skip_security_attributes = set() |
1012 return self._skip_security_attributes |
1048 return self.__cw_skip_security_attributes |
1013 |
1049 |
1014 def set_defaults(self): |
1050 def _cw_set_defaults(self): |
1015 """set default values according to the schema""" |
1051 """set default values according to the schema""" |
1016 for attr, value in self.e_schema.defaults(): |
1052 for attr, value in self.e_schema.defaults(): |
1017 if not self.has_key(attr): |
1053 if not self.cw_attr_cache.has_key(attr): |
1018 self[str(attr)] = value |
1054 self[str(attr)] = value |
1019 |
1055 |
1020 def check(self, creation=False): |
1056 def _cw_check(self, creation=False): |
1021 """check this entity against its schema. Only final relation |
1057 """check this entity against its schema. Only final relation |
1022 are checked here, constraint on actual relations are checked in hooks |
1058 are checked here, constraint on actual relations are checked in hooks |
1023 """ |
1059 """ |
1024 # necessary since eid is handled specifically and yams require it to be |
1060 # necessary since eid is handled specifically and yams require it to be |
1025 # in the dictionary |
1061 # in the dictionary |
1038 else: |
1074 else: |
1039 relations = None |
1075 relations = None |
1040 self.e_schema.check(self, creation=creation, _=_, |
1076 self.e_schema.check(self, creation=creation, _=_, |
1041 relations=relations) |
1077 relations=relations) |
1042 |
1078 |
1043 def fti_containers(self, _done=None): |
1079 @deprecated('[3.9] use entity.cw_attr_value(attr)') |
1044 if _done is None: |
1080 def get_value(self, name): |
1045 _done = set() |
1081 return self.cw_attr_value(name) |
1046 _done.add(self.eid) |
1082 |
1047 containers = tuple(self.e_schema.fulltext_containers()) |
1083 @deprecated('[3.9] use entity.cw_delete()') |
1048 if containers: |
1084 def delete(self, **kwargs): |
1049 for rschema, target in containers: |
1085 return self.cw_delete(**kwargs) |
1050 if target == 'object': |
1086 |
1051 targets = getattr(self, rschema.type) |
1087 @deprecated('[3.9] use entity.cw_attr_metadata(attr, metadata)') |
1052 else: |
1088 def attr_metadata(self, attr, metadata): |
1053 targets = getattr(self, 'reverse_%s' % rschema) |
1089 return self.cw_attr_metadata(attr, metadata) |
1054 for entity in targets: |
1090 |
1055 if entity.eid in _done: |
1091 @deprecated('[3.9] use entity.cw_has_perm(action)') |
1056 continue |
1092 def has_perm(self, action): |
1057 for container in entity.fti_containers(_done): |
1093 return self.cw_has_perm(action) |
1058 yield container |
1094 |
1059 yielded = True |
1095 @deprecated('[3.9] use entity.cw_set_relation_cache(rtype, role, rset)') |
1060 else: |
1096 def set_related_cache(self, rtype, role, rset): |
1061 yield self |
1097 self.cw_set_relation_cache(rtype, role, rset) |
1062 |
1098 |
1063 def get_words(self): |
1099 @deprecated('[3.9] use entity.cw_clear_relation_cache(rtype, role, rset)') |
1064 """used by the full text indexer to get words to index |
1100 def clear_related_cache(self, rtype=None, role=None): |
1065 |
1101 self.cw_clear_relation_cache(rtype, role) |
1066 this method should only be used on the repository side since it depends |
1102 |
1067 on the logilab.database package |
1103 @deprecated('[3.9] use entity.cw_related_rql(rtype, [role, [targettypes]])') |
1068 |
1104 def related_rql(self, rtype, role='subject', targettypes=None): |
1069 :rtype: list |
1105 return self.cw_related_rql(rtype, role, targettypes) |
1070 :return: the list of indexable word of this entity |
|
1071 """ |
|
1072 from logilab.database.fti import tokenize |
|
1073 # take care to cases where we're modyfying the schema |
|
1074 pending = self._cw.transaction_data.setdefault('pendingrdefs', set()) |
|
1075 words = [] |
|
1076 for rschema in self.e_schema.indexable_attributes(): |
|
1077 if (self.e_schema, rschema) in pending: |
|
1078 continue |
|
1079 try: |
|
1080 value = self.printable_value(rschema, format='text/plain') |
|
1081 except TransformError: |
|
1082 continue |
|
1083 except: |
|
1084 self.exception("can't add value of %s to text index for entity %s", |
|
1085 rschema, self.eid) |
|
1086 continue |
|
1087 if value: |
|
1088 words += tokenize(value) |
|
1089 for rschema, role in self.e_schema.fulltext_relations(): |
|
1090 if role == 'subject': |
|
1091 for entity in getattr(self, rschema.type): |
|
1092 words += entity.get_words() |
|
1093 else: # if role == 'object': |
|
1094 for entity in getattr(self, 'reverse_%s' % rschema.type): |
|
1095 words += entity.get_words() |
|
1096 return words |
|
1097 |
1106 |
1098 |
1107 |
1099 # attribute and relation descriptors ########################################## |
1108 # attribute and relation descriptors ########################################## |
1100 |
1109 |
1101 class Attribute(object): |
1110 class Attribute(object): |