entity.py
changeset 5557 1a534c596bff
parent 5556 9ab2b4c74baf
child 5559 6b183d860295
equal deleted inserted replaced
5556:9ab2b4c74baf 5557:1a534c596bff
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
    14 # details.
    14 # details.
    15 #
    15 #
    16 # You should have received a copy of the GNU Lesser General Public License along
    16 # You should have received a copy of the GNU Lesser General Public License along
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    18 """Base class for entity objects manipulated in clients
    18 """Base class for entity objects manipulated in clients"""
    19 
    19 
    20 """
       
    21 __docformat__ = "restructuredtext en"
    20 __docformat__ = "restructuredtext en"
    22 
    21 
    23 from warnings import warn
    22 from warnings import warn
    24 
    23 
    25 from logilab.common import interface
    24 from logilab.common import interface
    26 from logilab.common.compat import all
       
    27 from logilab.common.decorators import cached
    25 from logilab.common.decorators import cached
       
    26 from logilab.common.deprecation import deprecated
    28 from logilab.mtconverter import TransformData, TransformError, xml_escape
    27 from logilab.mtconverter import TransformData, TransformError, xml_escape
    29 
    28 
    30 from rql.utils import rqlvar_maker
    29 from rql.utils import rqlvar_maker
    31 
    30 
    32 from cubicweb import Unauthorized, typed_eid
    31 from cubicweb import Unauthorized, typed_eid
   287         return created
   286         return created
   288 
   287 
   289     def __init__(self, req, rset=None, row=None, col=0):
   288     def __init__(self, req, rset=None, row=None, col=0):
   290         AppObject.__init__(self, req, rset=rset, row=row, col=col)
   289         AppObject.__init__(self, req, rset=rset, row=row, col=col)
   291         dict.__init__(self)
   290         dict.__init__(self)
   292         self._related_cache = {}
   291         self._cw_related_cache = {}
   293         if rset is not None:
   292         if rset is not None:
   294             self.eid = rset[row][col]
   293             self.eid = rset[row][col]
   295         else:
   294         else:
   296             self.eid = None
   295             self.eid = None
   297         self._is_saved = True
   296         self._cw_is_saved = True
   298 
   297 
   299     def __repr__(self):
   298     def __repr__(self):
   300         return '<Entity %s %s %s at %s>' % (
   299         return '<Entity %s %s %s at %s>' % (
   301             self.e_schema, self.eid, self.keys(), id(self))
   300             self.e_schema, self.eid, self.keys(), id(self))
   302 
   301 
   411             adapter = self._cw.vreg['adapters'].select_or_none(
   410             adapter = self._cw.vreg['adapters'].select_or_none(
   412                 interface, self._cw, entity=self)
   411                 interface, self._cw, entity=self)
   413             cache[interface] = adapter
   412             cache[interface] = adapter
   414             return adapter
   413             return adapter
   415 
   414 
   416     def rql_set_value(self, attr, value):
   415     def has_eid(self): # XXX cw_has_eid
   417         """call by rql execution plan when some attribute is modified
       
   418 
       
   419         don't use dict api in such case since we don't want attribute to be
       
   420         added to skip_security_attributes.
       
   421         """
       
   422         super(Entity, self).__setitem__(attr, value)
       
   423 
       
   424     def pre_add_hook(self):
       
   425         """hook called by the repository before doing anything to add the entity
       
   426         (before_add entity hooks have not been called yet). This give the
       
   427         occasion to do weird stuff such as autocast (File -> Image for instance).
       
   428 
       
   429         This method must return the actual entity to be added.
       
   430         """
       
   431         return self
       
   432 
       
   433     def set_eid(self, eid):
       
   434         self.eid = eid
       
   435 
       
   436     def has_eid(self):
       
   437         """return True if the entity has an attributed eid (False
   416         """return True if the entity has an attributed eid (False
   438         meaning that the entity has to be created
   417         meaning that the entity has to be created
   439         """
   418         """
   440         try:
   419         try:
   441             typed_eid(self.eid)
   420             typed_eid(self.eid)
   442             return True
   421             return True
   443         except (ValueError, TypeError):
   422         except (ValueError, TypeError):
   444             return False
   423             return False
   445 
   424 
   446     def is_saved(self):
   425     def cw_is_saved(self):
   447         """during entity creation, there is some time during which the entity
   426         """during entity creation, there is some time during which the entity
   448         has an eid attributed though it's not saved (eg during before_add_entity
   427         has an eid attributed though it's not saved (eg during
   449         hooks). You can use this method to ensure the entity has an eid *and* is
   428         'before_add_entity' hooks). You can use this method to ensure the entity
   450         saved in its source.
   429         has an eid *and* is saved in its source.
   451         """
   430         """
   452         return self.has_eid() and self._is_saved
   431         return self.has_eid() and self._cw_is_saved
   453 
   432 
   454     @cached
   433     @cached
   455     def metainformation(self):
   434     def cw_metainformation(self):
   456         res = dict(zip(('type', 'source', 'extid'), self._cw.describe(self.eid)))
   435         res = dict(zip(('type', 'source', 'extid'), self._cw.describe(self.eid)))
   457         res['source'] = self._cw.source_defs()[res['source']]
   436         res['source'] = self._cw.source_defs()[res['source']]
   458         return res
   437         return res
   459 
   438 
   460     def clear_local_perm_cache(self, action):
   439     def cw_check_perm(self, action):
   461         for rqlexpr in self.e_schema.get_rqlexprs(action):
       
   462             self._cw.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None)
       
   463 
       
   464     def check_perm(self, action):
       
   465         self.e_schema.check_perm(self._cw, action, eid=self.eid)
   440         self.e_schema.check_perm(self._cw, action, eid=self.eid)
   466 
   441 
   467     def has_perm(self, action):
   442     def cw_has_perm(self, action):
   468         return self.e_schema.has_perm(self._cw, action, eid=self.eid)
   443         return self.e_schema.has_perm(self._cw, action, eid=self.eid)
   469 
   444 
   470     def view(self, __vid, __registry='views', w=None, **kwargs):
   445     def view(self, __vid, __registry='views', w=None, **kwargs): # XXX cw_view
   471         """shortcut to apply a view on this entity"""
   446         """shortcut to apply a view on this entity"""
   472         view = self._cw.vreg[__registry].select(__vid, self._cw, rset=self.cw_rset,
   447         view = self._cw.vreg[__registry].select(__vid, self._cw, rset=self.cw_rset,
   473                                                 row=self.cw_row, col=self.cw_col,
   448                                                 row=self.cw_row, col=self.cw_col,
   474                                                 **kwargs)
   449                                                 **kwargs)
   475         return view.render(row=self.cw_row, col=self.cw_col, w=w, **kwargs)
   450         return view.render(row=self.cw_row, col=self.cw_col, w=w, **kwargs)
   476 
   451 
   477     def absolute_url(self, *args, **kwargs):
   452     def absolute_url(self, *args, **kwargs): # XXX cw_url
   478         """return an absolute url to view this entity"""
   453         """return an absolute url to view this entity"""
   479         # use *args since we don't want first argument to be "anonymous" to
   454         # use *args since we don't want first argument to be "anonymous" to
   480         # avoid potential clash with kwargs
   455         # avoid potential clash with kwargs
   481         if args:
   456         if args:
   482             assert len(args) == 1, 'only 0 or 1 non-named-argument expected'
   457             assert len(args) == 1, 'only 0 or 1 non-named-argument expected'
   485             method = None
   460             method = None
   486         # in linksearch mode, we don't want external urls else selecting
   461         # in linksearch mode, we don't want external urls else selecting
   487         # the object for use in the relation is tricky
   462         # the object for use in the relation is tricky
   488         # XXX search_state is web specific
   463         # XXX search_state is web specific
   489         if getattr(self._cw, 'search_state', ('normal',))[0] == 'normal':
   464         if getattr(self._cw, 'search_state', ('normal',))[0] == 'normal':
   490             kwargs['base_url'] = self.metainformation()['source'].get('base-url')
   465             kwargs['base_url'] = self.cw_metainformation()['source'].get('base-url')
   491         if method in (None, 'view'):
   466         if method in (None, 'view'):
   492             try:
   467             try:
   493                 kwargs['_restpath'] = self.rest_path(kwargs.get('base_url'))
   468                 kwargs['_restpath'] = self.rest_path(kwargs.get('base_url'))
   494             except TypeError:
   469             except TypeError:
   495                 warn('[3.4] %s: rest_path() now take use_ext_eid argument, '
   470                 warn('[3.4] %s: rest_path() now take use_ext_eid argument, '
   497                 kwargs['_restpath'] = self.rest_path()
   472                 kwargs['_restpath'] = self.rest_path()
   498         else:
   473         else:
   499             kwargs['rql'] = 'Any X WHERE X eid %s' % self.eid
   474             kwargs['rql'] = 'Any X WHERE X eid %s' % self.eid
   500         return self._cw.build_url(method, **kwargs)
   475         return self._cw.build_url(method, **kwargs)
   501 
   476 
   502     def rest_path(self, use_ext_eid=False):
   477     def rest_path(self, use_ext_eid=False): # XXX cw_rest_path
   503         """returns a REST-like (relative) path for this entity"""
   478         """returns a REST-like (relative) path for this entity"""
   504         mainattr, needcheck = self._rest_attr_info()
   479         mainattr, needcheck = self._rest_attr_info()
   505         etype = str(self.e_schema)
   480         etype = str(self.e_schema)
   506         path = etype.lower()
   481         path = etype.lower()
   507         if mainattr != 'eid':
   482         if mainattr != 'eid':
   520                 if nbresults != 1: # ambiguity?
   495                 if nbresults != 1: # ambiguity?
   521                     mainattr = 'eid'
   496                     mainattr = 'eid'
   522                     path += '/eid'
   497                     path += '/eid'
   523         if mainattr == 'eid':
   498         if mainattr == 'eid':
   524             if use_ext_eid:
   499             if use_ext_eid:
   525                 value = self.metainformation()['extid']
   500                 value = self.cw_metainformation()['extid']
   526             else:
   501             else:
   527                 value = self.eid
   502                 value = self.eid
   528         return '%s/%s' % (path, self._cw.url_quote(value))
   503         return '%s/%s' % (path, self._cw.url_quote(value))
   529 
   504 
   530     def attr_metadata(self, attr, metadata):
   505     def cw_attr_metadata(self, attr, metadata):
   531         """return a metadata for an attribute (None if unspecified)"""
   506         """return a metadata for an attribute (None if unspecified)"""
   532         value = getattr(self, '%s_%s' % (attr, metadata), None)
   507         value = getattr(self, '%s_%s' % (attr, metadata), None)
   533         if value is None and metadata == 'encoding':
   508         if value is None and metadata == 'encoding':
   534             value = self._cw.vreg.property_value('ui.encoding')
   509             value = self._cw.vreg.property_value('ui.encoding')
   535         return value
   510         return value
   536 
   511 
   537     def printable_value(self, attr, value=_marker, attrtype=None,
   512     def printable_value(self, attr, value=_marker, attrtype=None,
   538                         format='text/html', displaytime=True):
   513                         format='text/html', displaytime=True): # XXX cw_printable_value
   539         """return a displayable value (i.e. unicode string) which may contains
   514         """return a displayable value (i.e. unicode string) which may contains
   540         html tags
   515         html tags
   541         """
   516         """
   542         attr = str(attr)
   517         attr = str(attr)
   543         if value is _marker:
   518         if value is _marker:
   552         if attrtype == 'String':
   527         if attrtype == 'String':
   553             # internalinalized *and* formatted string such as schema
   528             # internalinalized *and* formatted string such as schema
   554             # description...
   529             # description...
   555             if props.internationalizable:
   530             if props.internationalizable:
   556                 value = self._cw._(value)
   531                 value = self._cw._(value)
   557             attrformat = self.attr_metadata(attr, 'format')
   532             attrformat = self.cw_attr_metadata(attr, 'format')
   558             if attrformat:
   533             if attrformat:
   559                 return self.mtc_transform(value, attrformat, format,
   534                 return self._cw_mtc_transform(value, attrformat, format,
   560                                           self._cw.encoding)
   535                                               self._cw.encoding)
   561         elif attrtype == 'Bytes':
   536         elif attrtype == 'Bytes':
   562             attrformat = self.attr_metadata(attr, 'format')
   537             attrformat = self.cw_attr_metadata(attr, 'format')
   563             if attrformat:
   538             if attrformat:
   564                 encoding = self.attr_metadata(attr, 'encoding')
   539                 encoding = self.cw_attr_metadata(attr, 'encoding')
   565                 return self.mtc_transform(value.getvalue(), attrformat, format,
   540                 return self._cw_mtc_transform(value.getvalue(), attrformat, format,
   566                                           encoding)
   541                                               encoding)
   567             return u''
   542             return u''
   568         value = printable_value(self._cw, attrtype, value, props,
   543         value = printable_value(self._cw, attrtype, value, props,
   569                                 displaytime=displaytime)
   544                                 displaytime=displaytime)
   570         if format == 'text/html':
   545         if format == 'text/html':
   571             value = xml_escape(value)
   546             value = xml_escape(value)
   572         return value
   547         return value
   573 
   548 
   574     def mtc_transform(self, data, format, target_format, encoding,
   549     def _cw_mtc_transform(self, data, format, target_format, encoding,
   575                       _engine=ENGINE):
   550                           _engine=ENGINE):
   576         trdata = TransformData(data, format, encoding, appobject=self)
   551         trdata = TransformData(data, format, encoding, appobject=self)
   577         data = _engine.convert(trdata, target_format).decode()
   552         data = _engine.convert(trdata, target_format).decode()
   578         if format == 'text/html':
   553         if format == 'text/html':
   579             data = soup2xhtml(data, self._cw.encoding)
   554             data = soup2xhtml(data, self._cw.encoding)
   580         return data
   555         return data
   581 
   556 
   582     # entity cloning ##########################################################
   557     # entity cloning ##########################################################
   583 
   558 
   584     def copy_relations(self, ceid):
   559     def copy_relations(self, ceid): # XXX cw_copy_relations
   585         """copy relations of the object with the given eid on this
   560         """copy relations of the object with the given eid on this
   586         object (this method is called on the newly created copy, and
   561         object (this method is called on the newly created copy, and
   587         ceid designates the original entity).
   562         ceid designates the original entity).
   588 
   563 
   589         By default meta and composite relations are skipped.
   564         By default meta and composite relations are skipped.
   608             if rdef.cardinality[1] in '?1':
   583             if rdef.cardinality[1] in '?1':
   609                 continue
   584                 continue
   610             rql = 'SET X %s V WHERE X eid %%(x)s, Y eid %%(y)s, Y %s V' % (
   585             rql = 'SET X %s V WHERE X eid %%(x)s, Y eid %%(y)s, Y %s V' % (
   611                 rschema.type, rschema.type)
   586                 rschema.type, rschema.type)
   612             execute(rql, {'x': self.eid, 'y': ceid})
   587             execute(rql, {'x': self.eid, 'y': ceid})
   613             self.clear_related_cache(rschema.type, 'subject')
   588             self.cw_clear_relation_cache(rschema.type, 'subject')
   614         for rschema in self.e_schema.object_relations():
   589         for rschema in self.e_schema.object_relations():
   615             if rschema.meta:
   590             if rschema.meta:
   616                 continue
   591                 continue
   617             # skip already defined relations
   592             # skip already defined relations
   618             if self.related(rschema.type, 'object'):
   593             if self.related(rschema.type, 'object'):
   626             if rdef.cardinality[0] in '?1':
   601             if rdef.cardinality[0] in '?1':
   627                 continue
   602                 continue
   628             rql = 'SET V %s X WHERE X eid %%(x)s, Y eid %%(y)s, V %s Y' % (
   603             rql = 'SET V %s X WHERE X eid %%(x)s, Y eid %%(y)s, V %s Y' % (
   629                 rschema.type, rschema.type)
   604                 rschema.type, rschema.type)
   630             execute(rql, {'x': self.eid, 'y': ceid})
   605             execute(rql, {'x': self.eid, 'y': ceid})
   631             self.clear_related_cache(rschema.type, 'object')
   606             self.cw_clear_relation_cache(rschema.type, 'object')
   632 
   607 
   633     # data fetching methods ###################################################
   608     # data fetching methods ###################################################
   634 
   609 
   635     @cached
   610     @cached
   636     def as_rset(self):
   611     def as_rset(self): # XXX .cw_as_rset
   637         """returns a resultset containing `self` information"""
   612         """returns a resultset containing `self` information"""
   638         rset = ResultSet([(self.eid,)], 'Any X WHERE X eid %(x)s',
   613         rset = ResultSet([(self.eid,)], 'Any X WHERE X eid %(x)s',
   639                          {'x': self.eid}, [(self.__regid__,)])
   614                          {'x': self.eid}, [(self.__regid__,)])
   640         rset.req = self._cw
   615         rset.req = self._cw
   641         return rset
   616         return rset
   642 
   617 
   643     def to_complete_relations(self):
   618     def _cw_to_complete_relations(self):
   644         """by default complete final relations to when calling .complete()"""
   619         """by default complete final relations to when calling .complete()"""
   645         for rschema in self.e_schema.subject_relations():
   620         for rschema in self.e_schema.subject_relations():
   646             if rschema.final:
   621             if rschema.final:
   647                 continue
   622                 continue
   648             targets = rschema.objects(self.e_schema)
   623             targets = rschema.objects(self.e_schema)
   655                 rdef = rschema.rdef(self.e_schema, targets[0])
   630                 rdef = rschema.rdef(self.e_schema, targets[0])
   656                 if matching_groups(rdef.get_groups('read')) and \
   631                 if matching_groups(rdef.get_groups('read')) and \
   657                    all(matching_groups(e.get_groups('read')) for e in targets):
   632                    all(matching_groups(e.get_groups('read')) for e in targets):
   658                     yield rschema, 'subject'
   633                     yield rschema, 'subject'
   659 
   634 
   660     def to_complete_attributes(self, skip_bytes=True, skip_pwd=True):
   635     def _cw_to_complete_attributes(self, skip_bytes=True, skip_pwd=True):
   661         for rschema, attrschema in self.e_schema.attribute_definitions():
   636         for rschema, attrschema in self.e_schema.attribute_definitions():
   662             # skip binary data by default
   637             # skip binary data by default
   663             if skip_bytes and attrschema.type == 'Bytes':
   638             if skip_bytes and attrschema.type == 'Bytes':
   664                 continue
   639                 continue
   665             attr = rschema.type
   640             attr = rschema.type
   672                 self[attr] = None
   647                 self[attr] = None
   673                 continue
   648                 continue
   674             yield attr
   649             yield attr
   675 
   650 
   676     _cw_completed = False
   651     _cw_completed = False
   677     def complete(self, attributes=None, skip_bytes=True, skip_pwd=True):
   652     def complete(self, attributes=None, skip_bytes=True, skip_pwd=True): # XXX cw_complete
   678         """complete this entity by adding missing attributes (i.e. query the
   653         """complete this entity by adding missing attributes (i.e. query the
   679         repository to fill the entity)
   654         repository to fill the entity)
   680 
   655 
   681         :type skip_bytes: bool
   656         :type skip_bytes: bool
   682         :param skip_bytes:
   657         :param skip_bytes:
   689             self._cw_completed = True
   664             self._cw_completed = True
   690         varmaker = rqlvar_maker()
   665         varmaker = rqlvar_maker()
   691         V = varmaker.next()
   666         V = varmaker.next()
   692         rql = ['WHERE %s eid %%(x)s' % V]
   667         rql = ['WHERE %s eid %%(x)s' % V]
   693         selected = []
   668         selected = []
   694         for attr in (attributes or self.to_complete_attributes(skip_bytes, skip_pwd)):
   669         for attr in (attributes or self._cw_to_complete_attributes(skip_bytes, skip_pwd)):
   695             # if attribute already in entity, nothing to do
   670             # if attribute already in entity, nothing to do
   696             if self.has_key(attr):
   671             if self.has_key(attr):
   697                 continue
   672                 continue
   698             # case where attribute must be completed, but is not yet in entity
   673             # case where attribute must be completed, but is not yet in entity
   699             var = varmaker.next()
   674             var = varmaker.next()
   701             selected.append((attr, var))
   676             selected.append((attr, var))
   702         # +1 since this doen't include the main variable
   677         # +1 since this doen't include the main variable
   703         lastattr = len(selected) + 1
   678         lastattr = len(selected) + 1
   704         if attributes is None:
   679         if attributes is None:
   705             # fetch additional relations (restricted to 0..1 relations)
   680             # fetch additional relations (restricted to 0..1 relations)
   706             for rschema, role in self.to_complete_relations():
   681             for rschema, role in self._cw_to_complete_relations():
   707                 rtype = rschema.type
   682                 rtype = rschema.type
   708                 if self.relation_cached(rtype, role):
   683                 if self.cw_relation_cached(rtype, role):
   709                     continue
   684                     continue
   710                 var = varmaker.next()
   685                 var = varmaker.next()
   711                 targettype = rschema.targets(self.e_schema, role)[0]
   686                 targettype = rschema.targets(self.e_schema, role)[0]
   712                 rdef = rschema.role_rdef(self.e_schema, targettype, role)
   687                 rdef = rschema.role_rdef(self.e_schema, targettype, role)
   713                 card = rdef.role_cardinality(role)
   688                 card = rdef.role_cardinality(role)
   740                 if value is None:
   715                 if value is None:
   741                     rrset = ResultSet([], rql, {'x': self.eid})
   716                     rrset = ResultSet([], rql, {'x': self.eid})
   742                     rrset.req = self._cw
   717                     rrset.req = self._cw
   743                 else:
   718                 else:
   744                     rrset = self._cw.eid_rset(value)
   719                     rrset = self._cw.eid_rset(value)
   745                 self.set_related_cache(rtype, role, rrset)
   720                 self.cw_set_relation_cache(rtype, role, rrset)
   746 
   721 
   747     def get_value(self, name):
   722     def cw_attr_value(self, name):
   748         """get value for the attribute relation <name>, query the repository
   723         """get value for the attribute relation <name>, query the repository
   749         to get the value if necessary.
   724         to get the value if necessary.
   750 
   725 
   751         :type name: str
   726         :type name: str
   752         :param name: name of the attribute to get
   727         :param name: name of the attribute to get
   753         """
   728         """
   754         try:
   729         try:
   755             value = self[name]
   730             value = self[name]
   756         except KeyError:
   731         except KeyError:
   757             if not self.is_saved():
   732             if not self.cw_is_saved():
   758                 return None
   733                 return None
   759             rql = "Any A WHERE X eid %%(x)s, X %s A" % name
   734             rql = "Any A WHERE X eid %%(x)s, X %s A" % name
   760             try:
   735             try:
   761                 rset = self._cw.execute(rql, {'x': self.eid})
   736                 rset = self._cw.execute(rql, {'x': self.eid})
   762             except Unauthorized:
   737             except Unauthorized:
   774                         self[name] = value = self._cw._('unaccessible')
   749                         self[name] = value = self._cw._('unaccessible')
   775                     else:
   750                     else:
   776                         self[name] = value = None
   751                         self[name] = value = None
   777         return value
   752         return value
   778 
   753 
   779     def related(self, rtype, role='subject', limit=None, entities=False):
   754     def related(self, rtype, role='subject', limit=None, entities=False): # XXX .cw_related
   780         """returns a resultset of related entities
   755         """returns a resultset of related entities
   781 
   756 
   782         :param role: is the role played by 'self' in the relation ('subject' or 'object')
   757         :param role: is the role played by 'self' in the relation ('subject' or 'object')
   783         :param limit: resultset's maximum size
   758         :param limit: resultset's maximum size
   784         :param entities: if True, the entites are returned; if False, a result set is returned
   759         :param entities: if True, the entites are returned; if False, a result set is returned
   785         """
   760         """
   786         try:
   761         try:
   787             return self.related_cache(rtype, role, entities, limit)
   762             return self._cw_relation_cache(rtype, role, entities, limit)
   788         except KeyError:
   763         except KeyError:
   789             pass
   764             pass
   790         assert self.has_eid()
   765         assert self.has_eid()
   791         rql = self.related_rql(rtype, role)
   766         rql = self.cw_related_rql(rtype, role)
   792         rset = self._cw.execute(rql, {'x': self.eid})
   767         rset = self._cw.execute(rql, {'x': self.eid})
   793         self.set_related_cache(rtype, role, rset)
   768         self.cw_set_relation_cache(rtype, role, rset)
   794         return self.related(rtype, role, limit, entities)
   769         return self.related(rtype, role, limit, entities)
   795 
   770 
   796     def related_rql(self, rtype, role='subject', targettypes=None):
   771     def cw_related_rql(self, rtype, role='subject', targettypes=None):
   797         rschema = self._cw.vreg.schema[rtype]
   772         rschema = self._cw.vreg.schema[rtype]
   798         if role == 'subject':
   773         if role == 'subject':
   799             restriction = 'E eid %%(x)s, E %s X' % rtype
   774             restriction = 'E eid %%(x)s, E %s X' % rtype
   800             if targettypes is None:
   775             if targettypes is None:
   801                 targettypes = rschema.objects(self.e_schema)
   776                 targettypes = rschema.objects(self.e_schema)
   840                       tuple(args)
   815                       tuple(args)
   841         return rql
   816         return rql
   842 
   817 
   843     # generic vocabulary methods ##############################################
   818     # generic vocabulary methods ##############################################
   844 
   819 
   845     def unrelated_rql(self, rtype, targettype, role, ordermethod=None,
   820     def cw_unrelated_rql(self, rtype, targettype, role, ordermethod=None,
   846                       vocabconstraints=True):
   821                       vocabconstraints=True):
   847         """build a rql to fetch `targettype` entities unrelated to this entity
   822         """build a rql to fetch `targettype` entities unrelated to this entity
   848         using (rtype, role) relation.
   823         using (rtype, role) relation.
   849 
   824 
   850         Consider relation permissions so that returned entities may be actually
   825         Consider relation permissions so that returned entities may be actually
   902                                  select.solutions, args, existant)
   877                                  select.solutions, args, existant)
   903             rql = rqlst.as_string()
   878             rql = rqlst.as_string()
   904         return rql, args
   879         return rql, args
   905 
   880 
   906     def unrelated(self, rtype, targettype, role='subject', limit=None,
   881     def unrelated(self, rtype, targettype, role='subject', limit=None,
   907                   ordermethod=None):
   882                   ordermethod=None): # XXX .cw_unrelated
   908         """return a result set of target type objects that may be related
   883         """return a result set of target type objects that may be related
   909         by a given relation, with self as subject or object
   884         by a given relation, with self as subject or object
   910         """
   885         """
   911         try:
   886         try:
   912             rql, args = self.unrelated_rql(rtype, targettype, role, ordermethod)
   887             rql, args = self.cw_unrelated_rql(rtype, targettype, role, ordermethod)
   913         except Unauthorized:
   888         except Unauthorized:
   914             return self._cw.empty_rset()
   889             return self._cw.empty_rset()
   915         if limit is not None:
   890         if limit is not None:
   916             before, after = rql.split(' WHERE ', 1)
   891             before, after = rql.split(' WHERE ', 1)
   917             rql = '%s LIMIT %s WHERE %s' % (before, limit, after)
   892             rql = '%s LIMIT %s WHERE %s' % (before, limit, after)
   918         return self._cw.execute(rql, args)
   893         return self._cw.execute(rql, args)
   919 
   894 
   920     # relations cache handling ################################################
   895     # relations cache handling #################################################
   921 
   896 
   922     def relation_cached(self, rtype, role):
   897     def cw_relation_cached(self, rtype, role):
   923         """return true if the given relation is already cached on the instance
   898         """return None if the given relation isn't already cached on the
   924         """
   899         instance, else the content of the cache (a 2-uple (rset, entities)).
   925         return self._related_cache.get('%s_%s' % (rtype, role))
   900         """
   926 
   901         return self._cw_related_cache.get('%s_%s' % (rtype, role))
   927     def related_cache(self, rtype, role, entities=True, limit=None):
   902 
       
   903     def _cw_relation_cache(self, rtype, role, entities=True, limit=None):
   928         """return values for the given relation if it's cached on the instance,
   904         """return values for the given relation if it's cached on the instance,
   929         else raise `KeyError`
   905         else raise `KeyError`
   930         """
   906         """
   931         res = self._related_cache['%s_%s' % (rtype, role)][entities]
   907         res = self._cw_related_cache['%s_%s' % (rtype, role)][entities]
   932         if limit is not None and limit < len(res):
   908         if limit is not None and limit < len(res):
   933             if entities:
   909             if entities:
   934                 res = res[:limit]
   910                 res = res[:limit]
   935             else:
   911             else:
   936                 res = res.limit(limit)
   912                 res = res.limit(limit)
   937         return res
   913         return res
   938 
   914 
   939     def set_related_cache(self, rtype, role, rset, col=0):
   915     def cw_set_relation_cache(self, rtype, role, rset):
   940         """set cached values for the given relation"""
   916         """set cached values for the given relation"""
   941         if rset:
   917         if rset:
   942             related = list(rset.entities(col))
   918             related = list(rset.entities(0))
   943             rschema = self._cw.vreg.schema.rschema(rtype)
   919             rschema = self._cw.vreg.schema.rschema(rtype)
   944             if role == 'subject':
   920             if role == 'subject':
   945                 rcard = rschema.rdef(self.e_schema, related[0].e_schema).cardinality[1]
   921                 rcard = rschema.rdef(self.e_schema, related[0].e_schema).cardinality[1]
   946                 target = 'object'
   922                 target = 'object'
   947             else:
   923             else:
   948                 rcard = rschema.rdef(related[0].e_schema, self.e_schema).cardinality[0]
   924                 rcard = rschema.rdef(related[0].e_schema, self.e_schema).cardinality[0]
   949                 target = 'subject'
   925                 target = 'subject'
   950             if rcard in '?1':
   926             if rcard in '?1':
   951                 for rentity in related:
   927                 for rentity in related:
   952                     rentity._related_cache['%s_%s' % (rtype, target)] = (
   928                     rentity._cw_related_cache['%s_%s' % (rtype, target)] = (
   953                         self.as_rset(), (self,))
   929                         self.as_rset(), (self,))
   954         else:
   930         else:
   955             related = ()
   931             related = ()
   956         self._related_cache['%s_%s' % (rtype, role)] = (rset, related)
   932         self._cw_related_cache['%s_%s' % (rtype, role)] = (rset, related)
   957 
   933 
   958     def clear_related_cache(self, rtype=None, role=None):
   934     def cw_clear_relation_cache(self, rtype=None, role=None):
   959         """clear cached values for the given relation or the entire cache if
   935         """clear cached values for the given relation or the entire cache if
   960         no relation is given
   936         no relation is given
   961         """
   937         """
   962         if rtype is None:
   938         if rtype is None:
   963             self._related_cache = {}
   939             self._cw_related_cache = {}
   964         else:
   940         else:
   965             assert role
   941             assert role
   966             self._related_cache.pop('%s_%s' % (rtype, role), None)
   942             self._cw_related_cache.pop('%s_%s' % (rtype, role), None)
   967 
   943 
   968     def clear_all_caches(self):
   944     def clear_all_caches(self): # XXX cw_clear_all_caches
   969         """flush all caches on this entity. Further attributes/relations access
   945         """flush all caches on this entity. Further attributes/relations access
   970         will triggers new database queries to get back values.
   946         will triggers new database queries to get back values.
   971 
   947 
   972         If you use custom caches on your entity class (take care to @cached!),
   948         If you use custom caches on your entity class (take care to @cached!),
   973         you should override this method to clear them as well.
   949         you should override this method to clear them as well.
   976         haseid = 'eid' in self
   952         haseid = 'eid' in self
   977         self._cw_completed = False
   953         self._cw_completed = False
   978         self.clear()
   954         self.clear()
   979         # clear relations cache
   955         # clear relations cache
   980         for rschema, _, role in self.e_schema.relation_definitions():
   956         for rschema, _, role in self.e_schema.relation_definitions():
   981             self.clear_related_cache(rschema.type, role)
   957             self.cw_clear_relation_cache(rschema.type, role)
   982         # rest path unique cache
   958         # rest path unique cache
   983         try:
   959         try:
   984             del self.__unique
   960             del self.__unique
   985         except AttributeError:
   961         except AttributeError:
   986             pass
   962             pass
   989         except AttributeError:
   965         except AttributeError:
   990             pass
   966             pass
   991 
   967 
   992     # raw edition utilities ###################################################
   968     # raw edition utilities ###################################################
   993 
   969 
   994     def set_attributes(self, **kwargs):
   970     def set_attributes(self, **kwargs): # XXX cw_set_attributes
   995         _check_cw_unsafe(kwargs)
   971         _check_cw_unsafe(kwargs)
   996         assert kwargs
   972         assert kwargs
   997         assert self._is_saved, "should not call set_attributes while entity "\
   973         assert self.cw_is_saved(), "should not call set_attributes while entity "\
   998                "hasn't been saved yet"
   974                "hasn't been saved yet"
   999         relations = []
   975         relations = []
  1000         for key in kwargs:
   976         for key in kwargs:
  1001             relations.append('X %s %%(%s)s' % (key, key))
   977             relations.append('X %s %%(%s)s' % (key, key))
  1002         # and now update the database
   978         # and now update the database
  1007         # update current local object _after_ the rql query to avoid
   983         # update current local object _after_ the rql query to avoid
  1008         # interferences between the query execution itself and the
   984         # interferences between the query execution itself and the
  1009         # edited_attributes / skip_security_attributes machinery
   985         # edited_attributes / skip_security_attributes machinery
  1010         self.update(kwargs)
   986         self.update(kwargs)
  1011 
   987 
  1012     def set_relations(self, **kwargs):
   988     def set_relations(self, **kwargs): # XXX cw_set_relations
  1013         """add relations to the given object. To set a relation where this entity
   989         """add relations to the given object. To set a relation where this entity
  1014         is the object of the relation, use 'reverse_'<relation> as argument name.
   990         is the object of the relation, use 'reverse_'<relation> as argument name.
  1015 
   991 
  1016         Values may be an entity, a list of entities, or None (meaning that all
   992         Values may be an entity, a list of entities, or None (meaning that all
  1017         relations of the given type from or to this object should be deleted).
   993         relations of the given type from or to this object should be deleted).
  1031                 values = (values,)
  1007                 values = (values,)
  1032             self._cw.execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
  1008             self._cw.execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
  1033                 restr, ','.join(str(r.eid) for r in values)),
  1009                 restr, ','.join(str(r.eid) for r in values)),
  1034                              {'x': self.eid})
  1010                              {'x': self.eid})
  1035 
  1011 
  1036     def delete(self, **kwargs):
  1012     def cw_delete(self, **kwargs):
  1037         assert self.has_eid(), self.eid
  1013         assert self.has_eid(), self.eid
  1038         self._cw.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema,
  1014         self._cw.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema,
  1039                          {'x': self.eid}, **kwargs)
  1015                          {'x': self.eid}, **kwargs)
  1040 
  1016 
  1041     # server side utilities ###################################################
  1017     # server side utilities ###################################################
  1042 
  1018 
       
  1019     def _cw_rql_set_value(self, attr, value):
       
  1020         """call by rql execution plan when some attribute is modified
       
  1021 
       
  1022         don't use dict api in such case since we don't want attribute to be
       
  1023         added to skip_security_attributes.
       
  1024 
       
  1025         This method is for internal use, you should not use it.
       
  1026         """
       
  1027         super(Entity, self).__setitem__(attr, value)
       
  1028 
       
  1029     def _cw_clear_local_perm_cache(self, action):
       
  1030         for rqlexpr in self.e_schema.get_rqlexprs(action):
       
  1031             self._cw.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None)
       
  1032 
  1043     @property
  1033     @property
  1044     def skip_security_attributes(self):
  1034     def _cw_skip_security_attributes(self):
  1045         try:
  1035         try:
  1046             return self._skip_security_attributes
  1036             return self.__cw_skip_security_attributes
  1047         except:
  1037         except:
  1048             self._skip_security_attributes = set()
  1038             self.__cw_skip_security_attributes = set()
  1049             return self._skip_security_attributes
  1039             return self.__cw_skip_security_attributes
  1050 
  1040 
  1051     def set_defaults(self):
  1041     def _cw_set_defaults(self):
  1052         """set default values according to the schema"""
  1042         """set default values according to the schema"""
  1053         for attr, value in self.e_schema.defaults():
  1043         for attr, value in self.e_schema.defaults():
  1054             if not self.has_key(attr):
  1044             if not self.has_key(attr):
  1055                 self[str(attr)] = value
  1045                 self[str(attr)] = value
  1056 
  1046 
  1057     def check(self, creation=False):
  1047     def _cw_check(self, creation=False):
  1058         """check this entity against its schema. Only final relation
  1048         """check this entity against its schema. Only final relation
  1059         are checked here, constraint on actual relations are checked in hooks
  1049         are checked here, constraint on actual relations are checked in hooks
  1060         """
  1050         """
  1061         # necessary since eid is handled specifically and yams require it to be
  1051         # necessary since eid is handled specifically and yams require it to be
  1062         # in the dictionary
  1052         # in the dictionary
  1075         else:
  1065         else:
  1076             relations = None
  1066             relations = None
  1077         self.e_schema.check(self, creation=creation, _=_,
  1067         self.e_schema.check(self, creation=creation, _=_,
  1078                             relations=relations)
  1068                             relations=relations)
  1079 
  1069 
       
  1070     @deprecated('[3.9] use entity.cw_attr_value(attr)')
       
  1071     def get_value(self, name):
       
  1072         return self.cw_attr_value(name)
       
  1073 
       
  1074     @deprecated('[3.9] use entity.cw_delete()')
       
  1075     def delete(self, **kwargs):
       
  1076         return self.cw_delete(**kwargs)
       
  1077 
       
  1078     @deprecated('[3.9] use entity.cw_attr_metadata(attr, metadata)')
       
  1079     def attr_metadata(self, attr, metadata):
       
  1080         return self.cw_attr_metadata(attr, metadata)
       
  1081 
       
  1082     @deprecated('[3.9] use entity.cw_has_perm(action)')
       
  1083     def has_perm(self, action):
       
  1084         return self.cw_has_perm(action)
       
  1085 
       
  1086     @deprecated('[3.9] use entity.cw_set_relation_cache(rtype, role, rset)')
       
  1087     def set_related_cache(self, rtype, role, rset):
       
  1088         self.cw_set_relation_cache(rtype, role, rset)
       
  1089 
       
  1090     @deprecated('[3.9] use entity.cw_clear_relation_cache(rtype, role, rset)')
       
  1091     def clear_related_cache(self, rtype=None, role=None):
       
  1092         self.cw_clear_relation_cache(rtype, role)
  1080 
  1093 
  1081 # attribute and relation descriptors ##########################################
  1094 # attribute and relation descriptors ##########################################
  1082 
  1095 
  1083 class Attribute(object):
  1096 class Attribute(object):
  1084     """descriptor that controls schema attribute access"""
  1097     """descriptor that controls schema attribute access"""
  1088         self._attrname = attrname
  1101         self._attrname = attrname
  1089 
  1102 
  1090     def __get__(self, eobj, eclass):
  1103     def __get__(self, eobj, eclass):
  1091         if eobj is None:
  1104         if eobj is None:
  1092             return self
  1105             return self
  1093         return eobj.get_value(self._attrname)
  1106         return eobj.cw_attr_value(self._attrname)
  1094 
  1107 
  1095     def __set__(self, eobj, value):
  1108     def __set__(self, eobj, value):
  1096         eobj[self._attrname] = value
  1109         eobj[self._attrname] = value
  1097 
  1110 
  1098 
  1111