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