311 |
310 |
312 def __repr__(self): |
311 def __repr__(self): |
313 return '<Entity %s %s %s at %s>' % ( |
312 return '<Entity %s %s %s at %s>' % ( |
314 self.e_schema, self.eid, self.cw_attr_cache.keys(), id(self)) |
313 self.e_schema, self.eid, self.cw_attr_cache.keys(), id(self)) |
315 |
314 |
|
315 def __cmp__(self, other): |
|
316 raise NotImplementedError('comparison not implemented for %s' % self.__class__) |
|
317 |
316 def __json_encode__(self): |
318 def __json_encode__(self): |
317 """custom json dumps hook to dump the entity's eid |
319 """custom json dumps hook to dump the entity's eid |
318 which is not part of dict structure itself |
320 which is not part of dict structure itself |
319 """ |
321 """ |
320 dumpable = dict(self) |
322 dumpable = dict(self) |
321 dumpable['eid'] = self.eid |
323 dumpable['eid'] = self.eid |
322 return dumpable |
324 return dumpable |
323 |
|
324 def __nonzero__(self): |
|
325 return True |
|
326 |
|
327 def __hash__(self): |
|
328 return id(self) |
|
329 |
|
330 def __cmp__(self, other): |
|
331 raise NotImplementedError('comparison not implemented for %s' % self.__class__) |
|
332 |
|
333 def __contains__(self, key): |
|
334 return key in self.cw_attr_cache |
|
335 |
|
336 def __iter__(self): |
|
337 return iter(self.cw_attr_cache) |
|
338 |
|
339 def __getitem__(self, key): |
|
340 if key == 'eid': |
|
341 warn('[3.7] entity["eid"] is deprecated, use entity.eid instead', |
|
342 DeprecationWarning, stacklevel=2) |
|
343 return self.eid |
|
344 return self.cw_attr_cache[key] |
|
345 |
|
346 def __setitem__(self, attr, value): |
|
347 """override __setitem__ to update self.edited_attributes. |
|
348 |
|
349 Typically, a before_[update|add]_hook could do:: |
|
350 |
|
351 entity['generated_attr'] = generated_value |
|
352 |
|
353 and this way, edited_attributes will be updated accordingly. Also, add |
|
354 the attribute to skip_security since we don't want to check security |
|
355 for such attributes set by hooks. |
|
356 """ |
|
357 if attr == 'eid': |
|
358 warn('[3.7] entity["eid"] = value is deprecated, use entity.eid = value instead', |
|
359 DeprecationWarning, stacklevel=2) |
|
360 self.eid = value |
|
361 else: |
|
362 self.cw_attr_cache[attr] = value |
|
363 # don't add attribute into skip_security if already in edited |
|
364 # attributes, else we may accidentaly skip a desired security check |
|
365 if hasattr(self, 'edited_attributes') and \ |
|
366 attr not in self.edited_attributes: |
|
367 self.edited_attributes.add(attr) |
|
368 self._cw_skip_security_attributes.add(attr) |
|
369 |
|
370 def __delitem__(self, attr): |
|
371 """override __delitem__ to update self.edited_attributes on cleanup of |
|
372 undesired changes introduced in the entity's dict. For example, see the |
|
373 code snippet below from the `forge` cube: |
|
374 |
|
375 .. sourcecode:: python |
|
376 |
|
377 edited = self.entity.edited_attributes |
|
378 has_load_left = 'load_left' in edited |
|
379 if 'load' in edited and self.entity.load_left is None: |
|
380 self.entity.load_left = self.entity['load'] |
|
381 elif not has_load_left and edited: |
|
382 # cleanup, this may cause undesired changes |
|
383 del self.entity['load_left'] |
|
384 |
|
385 """ |
|
386 del self.cw_attr_cache[attr] |
|
387 if hasattr(self, 'edited_attributes'): |
|
388 self.edited_attributes.remove(attr) |
|
389 |
|
390 def clear(self): |
|
391 self.cw_attr_cache.clear() |
|
392 |
|
393 def get(self, key, default=None): |
|
394 return self.cw_attr_cache.get(key, default) |
|
395 |
|
396 def setdefault(self, attr, default): |
|
397 """override setdefault to update self.edited_attributes""" |
|
398 value = self.cw_attr_cache.setdefault(attr, default) |
|
399 # don't add attribute into skip_security if already in edited |
|
400 # attributes, else we may accidentaly skip a desired security check |
|
401 if hasattr(self, 'edited_attributes') and \ |
|
402 attr not in self.edited_attributes: |
|
403 self.edited_attributes.add(attr) |
|
404 self._cw_skip_security_attributes.add(attr) |
|
405 return value |
|
406 |
|
407 def pop(self, attr, default=_marker): |
|
408 """override pop to update self.edited_attributes on cleanup of |
|
409 undesired changes introduced in the entity's dict. See `__delitem__` |
|
410 """ |
|
411 if default is _marker: |
|
412 value = self.cw_attr_cache.pop(attr) |
|
413 else: |
|
414 value = self.cw_attr_cache.pop(attr, default) |
|
415 if hasattr(self, 'edited_attributes') and attr in self.edited_attributes: |
|
416 self.edited_attributes.remove(attr) |
|
417 return value |
|
418 |
|
419 def update(self, values): |
|
420 """override update to update self.edited_attributes. See `__setitem__` |
|
421 """ |
|
422 for attr, value in values.items(): |
|
423 self[attr] = value # use self.__setitem__ implementation |
|
424 |
325 |
425 def cw_adapt_to(self, interface): |
326 def cw_adapt_to(self, interface): |
426 """return an adapter the entity to the given interface name. |
327 """return an adapter the entity to the given interface name. |
427 |
328 |
428 return None if it can not be adapted. |
329 return None if it can not be adapted. |
736 if selected: |
631 if selected: |
737 # select V, we need it as the left most selected variable |
632 # select V, we need it as the left most selected variable |
738 # if some outer join are included to fetch inlined relations |
633 # if some outer join are included to fetch inlined relations |
739 rql = 'Any %s,%s %s' % (V, ','.join(var for attr, var in selected), |
634 rql = 'Any %s,%s %s' % (V, ','.join(var for attr, var in selected), |
740 ','.join(rql)) |
635 ','.join(rql)) |
741 rset = self._cw.execute(rql, {'x': self.eid}, build_descr=False)[0] |
636 try: |
|
637 rset = self._cw.execute(rql, {'x': self.eid}, build_descr=False)[0] |
|
638 except IndexError: |
|
639 raise Exception('unable to fetch attributes for entity with eid %s' |
|
640 % self.eid) |
742 # handle attributes |
641 # handle attributes |
743 for i in xrange(1, lastattr): |
642 for i in xrange(1, lastattr): |
744 self[str(selected[i-1][0])] = rset[i] |
643 self.cw_attr_cache[str(selected[i-1][0])] = rset[i] |
745 # handle relations |
644 # handle relations |
746 for i in xrange(lastattr, len(rset)): |
645 for i in xrange(lastattr, len(rset)): |
747 rtype, role = selected[i-1][0] |
646 rtype, role = selected[i-1][0] |
748 value = rset[i] |
647 value = rset[i] |
749 if value is None: |
648 if value is None: |
759 |
658 |
760 :type name: str |
659 :type name: str |
761 :param name: name of the attribute to get |
660 :param name: name of the attribute to get |
762 """ |
661 """ |
763 try: |
662 try: |
764 value = self.cw_attr_cache[name] |
663 return self.cw_attr_cache[name] |
765 except KeyError: |
664 except KeyError: |
766 if not self.cw_is_saved(): |
665 if not self.cw_is_saved(): |
767 return None |
666 return None |
768 rql = "Any A WHERE X eid %%(x)s, X %s A" % name |
667 rql = "Any A WHERE X eid %%(x)s, X %s A" % name |
769 try: |
668 try: |
770 rset = self._cw.execute(rql, {'x': self.eid}) |
669 rset = self._cw.execute(rql, {'x': self.eid}) |
771 except Unauthorized: |
670 except Unauthorized: |
772 self[name] = value = None |
671 self.cw_attr_cache[name] = value = None |
773 else: |
672 else: |
774 assert rset.rowcount <= 1, (self, rql, rset.rowcount) |
673 assert rset.rowcount <= 1, (self, rql, rset.rowcount) |
775 try: |
674 try: |
776 self[name] = value = rset.rows[0][0] |
675 self.cw_attr_cache[name] = value = rset.rows[0][0] |
777 except IndexError: |
676 except IndexError: |
778 # probably a multisource error |
677 # probably a multisource error |
779 self.critical("can't get value for attribute %s of entity with eid %s", |
678 self.critical("can't get value for attribute %s of entity with eid %s", |
780 name, self.eid) |
679 name, self.eid) |
781 if self.e_schema.destination(name) == 'String': |
680 if self.e_schema.destination(name) == 'String': |
782 # XXX (syt) imo emtpy string is better |
681 self.cw_attr_cache[name] = value = self._cw._('unaccessible') |
783 self[name] = value = self._cw._('unaccessible') |
|
784 else: |
682 else: |
785 self[name] = value = None |
683 self.cw_attr_cache[name] = value = None |
786 return value |
684 return value |
787 |
685 |
788 def related(self, rtype, role='subject', limit=None, entities=False): # XXX .cw_related |
686 def related(self, rtype, role='subject', limit=None, entities=False): # XXX .cw_related |
789 """returns a resultset of related entities |
687 """returns a resultset of related entities |
790 |
688 |
791 :param role: is the role played by 'self' in the relation ('subject' or 'object') |
689 :param role: is the role played by 'self' in the relation ('subject' or 'object') |
1045 def cw_delete(self, **kwargs): |
942 def cw_delete(self, **kwargs): |
1046 assert self.has_eid(), self.eid |
943 assert self.has_eid(), self.eid |
1047 self._cw.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema, |
944 self._cw.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema, |
1048 {'x': self.eid}, **kwargs) |
945 {'x': self.eid}, **kwargs) |
1049 |
946 |
1050 # server side utilities ################################################### |
947 # server side utilities #################################################### |
1051 |
|
1052 def _cw_rql_set_value(self, attr, value): |
|
1053 """call by rql execution plan when some attribute is modified |
|
1054 |
|
1055 don't use dict api in such case since we don't want attribute to be |
|
1056 added to skip_security_attributes. |
|
1057 |
|
1058 This method is for internal use, you should not use it. |
|
1059 """ |
|
1060 self.cw_attr_cache[attr] = value |
|
1061 |
948 |
1062 def _cw_clear_local_perm_cache(self, action): |
949 def _cw_clear_local_perm_cache(self, action): |
1063 for rqlexpr in self.e_schema.get_rqlexprs(action): |
950 for rqlexpr in self.e_schema.get_rqlexprs(action): |
1064 self._cw.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None) |
951 self._cw.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None) |
1065 |
952 |
1066 @property |
953 # deprecated stuff ######################################################### |
1067 def _cw_skip_security_attributes(self): |
|
1068 try: |
|
1069 return self.__cw_skip_security_attributes |
|
1070 except: |
|
1071 self.__cw_skip_security_attributes = set() |
|
1072 return self.__cw_skip_security_attributes |
|
1073 |
|
1074 def _cw_set_defaults(self): |
|
1075 """set default values according to the schema""" |
|
1076 for attr, value in self.e_schema.defaults(): |
|
1077 if not self.cw_attr_cache.has_key(attr): |
|
1078 self[str(attr)] = value |
|
1079 |
|
1080 def _cw_check(self, creation=False): |
|
1081 """check this entity against its schema. Only final relation |
|
1082 are checked here, constraint on actual relations are checked in hooks |
|
1083 """ |
|
1084 # necessary since eid is handled specifically and yams require it to be |
|
1085 # in the dictionary |
|
1086 if self._cw is None: |
|
1087 _ = unicode |
|
1088 else: |
|
1089 _ = self._cw._ |
|
1090 if creation: |
|
1091 # on creations, we want to check all relations, especially |
|
1092 # required attributes |
|
1093 relations = [rschema for rschema in self.e_schema.subject_relations() |
|
1094 if rschema.final and rschema.type != 'eid'] |
|
1095 elif hasattr(self, 'edited_attributes'): |
|
1096 relations = [self._cw.vreg.schema.rschema(rtype) |
|
1097 for rtype in self.edited_attributes] |
|
1098 else: |
|
1099 relations = None |
|
1100 self.e_schema.check(self, creation=creation, _=_, |
|
1101 relations=relations) |
|
1102 |
954 |
1103 @deprecated('[3.9] use entity.cw_attr_value(attr)') |
955 @deprecated('[3.9] use entity.cw_attr_value(attr)') |
1104 def get_value(self, name): |
956 def get_value(self, name): |
1105 return self.cw_attr_value(name) |
957 return self.cw_attr_value(name) |
1106 |
958 |
1118 |
970 |
1119 @deprecated('[3.9] use entity.cw_set_relation_cache(rtype, role, rset)') |
971 @deprecated('[3.9] use entity.cw_set_relation_cache(rtype, role, rset)') |
1120 def set_related_cache(self, rtype, role, rset): |
972 def set_related_cache(self, rtype, role, rset): |
1121 self.cw_set_relation_cache(rtype, role, rset) |
973 self.cw_set_relation_cache(rtype, role, rset) |
1122 |
974 |
1123 @deprecated('[3.9] use entity.cw_clear_relation_cache(rtype, role, rset)') |
975 @deprecated('[3.9] use entity.cw_clear_relation_cache(rtype, role)') |
1124 def clear_related_cache(self, rtype=None, role=None): |
976 def clear_related_cache(self, rtype=None, role=None): |
1125 self.cw_clear_relation_cache(rtype, role) |
977 self.cw_clear_relation_cache(rtype, role) |
1126 |
978 |
1127 @deprecated('[3.9] use entity.cw_related_rql(rtype, [role, [targettypes]])') |
979 @deprecated('[3.9] use entity.cw_related_rql(rtype, [role, [targettypes]])') |
1128 def related_rql(self, rtype, role='subject', targettypes=None): |
980 def related_rql(self, rtype, role='subject', targettypes=None): |
1129 return self.cw_related_rql(rtype, role, targettypes) |
981 return self.cw_related_rql(rtype, role, targettypes) |
|
982 |
|
983 @property |
|
984 @deprecated('[3.10] use entity.cw_edited') |
|
985 def edited_attributes(self): |
|
986 return self.cw_edited |
|
987 |
|
988 @property |
|
989 @deprecated('[3.10] use entity.cw_edited.skip_security') |
|
990 def skip_security_attributes(self): |
|
991 return self.cw_edited.skip_security |
|
992 |
|
993 @property |
|
994 @deprecated('[3.10] use entity.cw_edited.skip_security') |
|
995 def _cw_skip_security_attributes(self): |
|
996 return self.cw_edited.skip_security |
|
997 |
|
998 @property |
|
999 @deprecated('[3.10] use entity.cw_edited.querier_pending_relations') |
|
1000 def querier_pending_relations(self): |
|
1001 return self.cw_edited.querier_pending_relations |
|
1002 |
|
1003 @deprecated('[3.10] use key in entity.cw_attr_cache') |
|
1004 def __contains__(self, key): |
|
1005 return key in self.cw_attr_cache |
|
1006 |
|
1007 @deprecated('[3.10] iter on entity.cw_attr_cache') |
|
1008 def __iter__(self): |
|
1009 return iter(self.cw_attr_cache) |
|
1010 |
|
1011 @deprecated('[3.10] use entity.cw_attr_cache[attr]') |
|
1012 def __getitem__(self, key): |
|
1013 if key == 'eid': |
|
1014 warn('[3.7] entity["eid"] is deprecated, use entity.eid instead', |
|
1015 DeprecationWarning, stacklevel=2) |
|
1016 return self.eid |
|
1017 return self.cw_attr_cache[key] |
|
1018 |
|
1019 @deprecated('[3.10] use entity.cw_attr_cache.get(attr[, default])') |
|
1020 def get(self, key, default=None): |
|
1021 return self.cw_attr_cache.get(key, default) |
|
1022 |
|
1023 @deprecated('[3.10] use entity.cw_attr_cache.clear()') |
|
1024 def clear(self): |
|
1025 self.cw_attr_cache.clear() |
|
1026 # XXX clear cw_edited ? |
|
1027 |
|
1028 @deprecated('[3.10] use entity.cw_edited[attr] = value or entity.cw_attr_cache[attr] = value') |
|
1029 def __setitem__(self, attr, value): |
|
1030 """override __setitem__ to update self.cw_edited. |
|
1031 |
|
1032 Typically, a before_[update|add]_hook could do:: |
|
1033 |
|
1034 entity['generated_attr'] = generated_value |
|
1035 |
|
1036 and this way, cw_edited will be updated accordingly. Also, add |
|
1037 the attribute to skip_security since we don't want to check security |
|
1038 for such attributes set by hooks. |
|
1039 """ |
|
1040 if attr == 'eid': |
|
1041 warn('[3.7] entity["eid"] = value is deprecated, use entity.eid = value instead', |
|
1042 DeprecationWarning, stacklevel=2) |
|
1043 self.eid = value |
|
1044 else: |
|
1045 try: |
|
1046 self.cw_edited[attr] = value |
|
1047 except AttributeError: |
|
1048 self.cw_attr_cache[attr] = value |
|
1049 |
|
1050 @deprecated('[3.10] use del entity.cw_edited[attr]') |
|
1051 def __delitem__(self, attr): |
|
1052 """override __delitem__ to update self.cw_edited on cleanup of |
|
1053 undesired changes introduced in the entity's dict. For example, see the |
|
1054 code snippet below from the `forge` cube: |
|
1055 |
|
1056 .. sourcecode:: python |
|
1057 |
|
1058 edited = self.entity.cw_edited |
|
1059 has_load_left = 'load_left' in edited |
|
1060 if 'load' in edited and self.entity.load_left is None: |
|
1061 self.entity.load_left = self.entity['load'] |
|
1062 elif not has_load_left and edited: |
|
1063 # cleanup, this may cause undesired changes |
|
1064 del self.entity['load_left'] |
|
1065 """ |
|
1066 del self.cw_edited[attr] |
|
1067 |
|
1068 @deprecated('[3.10] use entity.cw_edited.setdefault(attr, default)') |
|
1069 def setdefault(self, attr, default): |
|
1070 """override setdefault to update self.cw_edited""" |
|
1071 return self.cw_edited.setdefault(attr, default) |
|
1072 |
|
1073 @deprecated('[3.10] use entity.cw_edited.pop(attr[, default])') |
|
1074 def pop(self, attr, *args): |
|
1075 """override pop to update self.cw_edited on cleanup of |
|
1076 undesired changes introduced in the entity's dict. See `__delitem__` |
|
1077 """ |
|
1078 return self.cw_edited.pop(attr, *args) |
|
1079 |
|
1080 @deprecated('[3.10] use entity.cw_edited.update(values)') |
|
1081 def update(self, values): |
|
1082 """override update to update self.cw_edited. See `__setitem__` |
|
1083 """ |
|
1084 self.cw_edited.update(values) |
1130 |
1085 |
1131 |
1086 |
1132 # attribute and relation descriptors ########################################## |
1087 # attribute and relation descriptors ########################################## |
1133 |
1088 |
1134 class Attribute(object): |
1089 class Attribute(object): |