105 mixins.append(mixin) |
105 mixins.append(mixin) |
106 for iface in getattr(mixin, '__implements__', ()): |
106 for iface in getattr(mixin, '__implements__', ()): |
107 if not interface.implements(cls, iface): |
107 if not interface.implements(cls, iface): |
108 interface.extend(cls, iface) |
108 interface.extend(cls, iface) |
109 if role == 'subject': |
109 if role == 'subject': |
110 setattr(cls, rschema.type, SubjectRelation(rschema)) |
110 attr = rschema.type |
111 else: |
111 else: |
112 attr = 'reverse_%s' % rschema.type |
112 attr = 'reverse_%s' % rschema.type |
113 setattr(cls, attr, ObjectRelation(rschema)) |
113 setattr(cls, attr, Relation(rschema, role)) |
114 if mixins: |
114 if mixins: |
115 # see etype class instantation in cwvreg.ETypeRegistry.etype_class method: |
115 # see etype class instantation in cwvreg.ETypeRegistry.etype_class method: |
116 # due to class dumping, cls is the generated top level class with actual |
116 # due to class dumping, cls is the generated top level class with actual |
117 # user class as (only) parent. Since we want to be able to override mixins |
117 # user class as (only) parent. Since we want to be able to override mixins |
118 # method from this user class, we have to take care to insert mixins after that |
118 # method from this user class, we have to take care to insert mixins after that |
122 # with some cases of entity classes inheritance. |
122 # with some cases of entity classes inheritance. |
123 mixins.insert(0, cls.__bases__[0]) |
123 mixins.insert(0, cls.__bases__[0]) |
124 mixins += cls.__bases__[1:] |
124 mixins += cls.__bases__[1:] |
125 cls.__bases__ = tuple(mixins) |
125 cls.__bases__ = tuple(mixins) |
126 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 |
127 |
145 |
128 @classmethod |
146 @classmethod |
129 def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X', |
147 def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X', |
130 settype=True, ordermethod='fetch_order'): |
148 settype=True, ordermethod='fetch_order'): |
131 """return a rql to fetch all entities of the class type""" |
149 """return a rql to fetch all entities of the class type""" |
375 def update(self, values): |
393 def update(self, values): |
376 """override update to update self.edited_attributes. See `__setitem__` |
394 """override update to update self.edited_attributes. See `__setitem__` |
377 """ |
395 """ |
378 for attr, value in values.items(): |
396 for attr, value in values.items(): |
379 self[attr] = value # use self.__setitem__ implementation |
397 self[attr] = value # use self.__setitem__ implementation |
|
398 |
|
399 def cw_adapt_to(self, interface): |
|
400 """return an adapter the entity to the given interface name. |
|
401 |
|
402 return None if it can not be adapted. |
|
403 """ |
|
404 try: |
|
405 cache = self._cw_adapters_cache |
|
406 except AttributeError: |
|
407 self._cw_adapters_cache = cache = {} |
|
408 try: |
|
409 return cache[interface] |
|
410 except KeyError: |
|
411 adapter = self._cw.vreg['adapters'].select_or_none( |
|
412 interface, self._cw, entity=self) |
|
413 cache[interface] = adapter |
|
414 return adapter |
380 |
415 |
381 def rql_set_value(self, attr, value): |
416 def rql_set_value(self, attr, value): |
382 """call by rql execution plan when some attribute is modified |
417 """call by rql execution plan when some attribute is modified |
383 |
418 |
384 don't use dict api in such case since we don't want attribute to be |
419 don't use dict api in such case since we don't want attribute to be |
947 # rest path unique cache |
982 # rest path unique cache |
948 try: |
983 try: |
949 del self.__unique |
984 del self.__unique |
950 except AttributeError: |
985 except AttributeError: |
951 pass |
986 pass |
|
987 try: |
|
988 del self._cw_adapters_cache |
|
989 except AttributeError: |
|
990 pass |
952 |
991 |
953 # raw edition utilities ################################################### |
992 # raw edition utilities ################################################### |
954 |
993 |
955 def set_attributes(self, **kwargs): |
994 def set_attributes(self, **kwargs): |
956 _check_cw_unsafe(kwargs) |
995 _check_cw_unsafe(kwargs) |
1036 else: |
1075 else: |
1037 relations = None |
1076 relations = None |
1038 self.e_schema.check(self, creation=creation, _=_, |
1077 self.e_schema.check(self, creation=creation, _=_, |
1039 relations=relations) |
1078 relations=relations) |
1040 |
1079 |
1041 def fti_containers(self, _done=None): |
|
1042 if _done is None: |
|
1043 _done = set() |
|
1044 _done.add(self.eid) |
|
1045 containers = tuple(self.e_schema.fulltext_containers()) |
|
1046 if containers: |
|
1047 for rschema, target in containers: |
|
1048 if target == 'object': |
|
1049 targets = getattr(self, rschema.type) |
|
1050 else: |
|
1051 targets = getattr(self, 'reverse_%s' % rschema) |
|
1052 for entity in targets: |
|
1053 if entity.eid in _done: |
|
1054 continue |
|
1055 for container in entity.fti_containers(_done): |
|
1056 yield container |
|
1057 yielded = True |
|
1058 else: |
|
1059 yield self |
|
1060 |
|
1061 def get_words(self): |
|
1062 """used by the full text indexer to get words to index |
|
1063 |
|
1064 this method should only be used on the repository side since it depends |
|
1065 on the logilab.database package |
|
1066 |
|
1067 :rtype: list |
|
1068 :return: the list of indexable word of this entity |
|
1069 """ |
|
1070 from logilab.database.fti import tokenize |
|
1071 # take care to cases where we're modyfying the schema |
|
1072 pending = self._cw.transaction_data.setdefault('pendingrdefs', set()) |
|
1073 words = [] |
|
1074 for rschema in self.e_schema.indexable_attributes(): |
|
1075 if (self.e_schema, rschema) in pending: |
|
1076 continue |
|
1077 try: |
|
1078 value = self.printable_value(rschema, format='text/plain') |
|
1079 except TransformError: |
|
1080 continue |
|
1081 except: |
|
1082 self.exception("can't add value of %s to text index for entity %s", |
|
1083 rschema, self.eid) |
|
1084 continue |
|
1085 if value: |
|
1086 words += tokenize(value) |
|
1087 for rschema, role in self.e_schema.fulltext_relations(): |
|
1088 if role == 'subject': |
|
1089 for entity in getattr(self, rschema.type): |
|
1090 words += entity.get_words() |
|
1091 else: # if role == 'object': |
|
1092 for entity in getattr(self, 'reverse_%s' % rschema.type): |
|
1093 words += entity.get_words() |
|
1094 return words |
|
1095 |
|
1096 |
1080 |
1097 # attribute and relation descriptors ########################################## |
1081 # attribute and relation descriptors ########################################## |
1098 |
1082 |
1099 class Attribute(object): |
1083 class Attribute(object): |
1100 """descriptor that controls schema attribute access""" |
1084 """descriptor that controls schema attribute access""" |
1109 return eobj.get_value(self._attrname) |
1093 return eobj.get_value(self._attrname) |
1110 |
1094 |
1111 def __set__(self, eobj, value): |
1095 def __set__(self, eobj, value): |
1112 eobj[self._attrname] = value |
1096 eobj[self._attrname] = value |
1113 |
1097 |
|
1098 |
1114 class Relation(object): |
1099 class Relation(object): |
1115 """descriptor that controls schema relation access""" |
1100 """descriptor that controls schema relation access""" |
1116 _role = None # for pylint |
1101 |
1117 |
1102 def __init__(self, rschema, role): |
1118 def __init__(self, rschema): |
|
1119 self._rschema = rschema |
|
1120 self._rtype = rschema.type |
1103 self._rtype = rschema.type |
|
1104 self._role = role |
1121 |
1105 |
1122 def __get__(self, eobj, eclass): |
1106 def __get__(self, eobj, eclass): |
1123 if eobj is None: |
1107 if eobj is None: |
1124 raise AttributeError('%s cannot be only be accessed from instances' |
1108 raise AttributeError('%s cannot be only be accessed from instances' |
1125 % self._rtype) |
1109 % self._rtype) |
1127 |
1111 |
1128 def __set__(self, eobj, value): |
1112 def __set__(self, eobj, value): |
1129 raise NotImplementedError |
1113 raise NotImplementedError |
1130 |
1114 |
1131 |
1115 |
1132 class SubjectRelation(Relation): |
|
1133 """descriptor that controls schema relation access""" |
|
1134 _role = 'subject' |
|
1135 |
|
1136 class ObjectRelation(Relation): |
|
1137 """descriptor that controls schema relation access""" |
|
1138 _role = 'object' |
|
1139 |
|
1140 from logging import getLogger |
1116 from logging import getLogger |
1141 from cubicweb import set_log_methods |
1117 from cubicweb import set_log_methods |
1142 set_log_methods(Entity, getLogger('cubicweb.entity')) |
1118 set_log_methods(Entity, getLogger('cubicweb.entity')) |