entity.py
changeset 5556 9ab2b4c74baf
parent 5522 6be95896d49e
child 5557 1a534c596bff
equal deleted inserted replaced
5555:a64f48dd5fe4 5556:9ab2b4c74baf
   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'))