entity.py
changeset 8483 4ba11607d84a
parent 8307 8be58694f416
child 8493 25da1e5c7c50
equal deleted inserted replaced
8469:82272decfa99 8483:4ba11607d84a
   450         if mainattr == 'eid':
   450         if mainattr == 'eid':
   451             needcheck = False
   451             needcheck = False
   452         return mainattr, needcheck
   452         return mainattr, needcheck
   453 
   453 
   454     @classmethod
   454     @classmethod
   455     def cw_instantiate(cls, execute, **kwargs):
   455     def _cw_build_entity_query(cls, kwargs):
   456         """add a new entity of this given type
       
   457 
       
   458         Example (in a shell session):
       
   459 
       
   460         >>> companycls = vreg['etypes'].etype_class(('Company')
       
   461         >>> personcls = vreg['etypes'].etype_class(('Person')
       
   462         >>> c = companycls.cw_instantiate(session.execute, name=u'Logilab')
       
   463         >>> p = personcls.cw_instantiate(session.execute, firstname=u'John', lastname=u'Doe',
       
   464         ...                              works_for=c)
       
   465 
       
   466         You can also set relation where the entity has 'object' role by
       
   467         prefixing the relation by 'reverse_'.
       
   468         """
       
   469         rql = 'INSERT %s X' % cls.__regid__
       
   470         relations = []
   456         relations = []
   471         restrictions = set()
   457         restrictions = set()
   472         pending_relations = []
   458         pending_relations = []
   473         eschema = cls.e_schema
   459         eschema = cls.e_schema
   474         qargs = {}
   460         qargs = {}
       
   461         attrcache = {}
   475         for attr, value in kwargs.items():
   462         for attr, value in kwargs.items():
   476             if attr.startswith('reverse_'):
   463             if attr.startswith('reverse_'):
   477                 attr = attr[len('reverse_'):]
   464                 attr = attr[len('reverse_'):]
   478                 role = 'object'
   465                 role = 'object'
   479             else:
   466             else:
   489                     # prepare IN clause
   476                     # prepare IN clause
   490                     pending_relations.append( (attr, role, value) )
   477                     pending_relations.append( (attr, role, value) )
   491                     continue
   478                     continue
   492             if rschema.final: # attribute
   479             if rschema.final: # attribute
   493                 relations.append('X %s %%(%s)s' % (attr, attr))
   480                 relations.append('X %s %%(%s)s' % (attr, attr))
       
   481                 attrcache[attr] = value
       
   482             elif value is None:
       
   483                 pending_relations.append( (attr, role, value) )
   494             else:
   484             else:
   495                 rvar = attr.upper()
   485                 rvar = attr.upper()
   496                 if role == 'object':
   486                 if role == 'object':
   497                     relations.append('%s %s X' % (rvar, attr))
   487                     relations.append('%s %s X' % (rvar, attr))
   498                 else:
   488                 else:
   501                 if not restriction in restrictions:
   491                 if not restriction in restrictions:
   502                     restrictions.add(restriction)
   492                     restrictions.add(restriction)
   503                 if hasattr(value, 'eid'):
   493                 if hasattr(value, 'eid'):
   504                     value = value.eid
   494                     value = value.eid
   505             qargs[attr] = value
   495             qargs[attr] = value
       
   496         rql = u''
   506         if relations:
   497         if relations:
   507             rql = '%s: %s' % (rql, ', '.join(relations))
   498             rql += ', '.join(relations)
   508         if restrictions:
   499         if restrictions:
   509             rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
   500             rql += ' WHERE %s' % ', '.join(restrictions)
   510         created = execute(rql, qargs).get_entity(0, 0)
   501         return rql, qargs, pending_relations, attrcache
       
   502 
       
   503     @classmethod
       
   504     def _cw_handle_pending_relations(cls, eid, pending_relations, execute):
   511         for attr, role, values in pending_relations:
   505         for attr, role, values in pending_relations:
   512             if role == 'object':
   506             if role == 'object':
   513                 restr = 'Y %s X' % attr
   507                 restr = 'Y %s X' % attr
   514             else:
   508             else:
   515                 restr = 'X %s Y' % attr
   509                 restr = 'X %s Y' % attr
       
   510             if values is None:
       
   511                 execute('DELETE %s WHERE X eid %%(x)s' % restr, {'x': eid})
       
   512                 continue
   516             execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
   513             execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
   517                 restr, ','.join(str(getattr(r, 'eid', r)) for r in values)),
   514                 restr, ','.join(str(getattr(r, 'eid', r)) for r in values)),
   518                     {'x': created.eid}, build_descr=False)
   515                     {'x': eid}, build_descr=False)
       
   516 
       
   517     @classmethod
       
   518     def cw_instantiate(cls, execute, **kwargs):
       
   519         """add a new entity of this given type
       
   520 
       
   521         Example (in a shell session):
       
   522 
       
   523         >>> companycls = vreg['etypes'].etype_class(('Company')
       
   524         >>> personcls = vreg['etypes'].etype_class(('Person')
       
   525         >>> c = companycls.cw_instantiate(session.execute, name=u'Logilab')
       
   526         >>> p = personcls.cw_instantiate(session.execute, firstname=u'John', lastname=u'Doe',
       
   527         ...                              works_for=c)
       
   528 
       
   529         You can also set relations where the entity has 'object' role by
       
   530         prefixing the relation name by 'reverse_'. Also, relation values may be
       
   531         an entity or eid, a list of entities or eids.
       
   532         """
       
   533         rql, qargs, pending_relations, attrcache = cls._cw_build_entity_query(kwargs)
       
   534         if rql:
       
   535             rql = 'INSERT %s X: %s' % (cls.__regid__, rql)
       
   536         else:
       
   537             rql = 'INSERT %s X' % (cls.__regid__)
       
   538         created = execute(rql, qargs).get_entity(0, 0)
       
   539         created.cw_attr_cache.update(attrcache)
       
   540         cls._cw_handle_pending_relations(created.eid, pending_relations, execute)
   519         return created
   541         return created
   520 
   542 
   521     def __init__(self, req, rset=None, row=None, col=0):
   543     def __init__(self, req, rset=None, row=None, col=0):
   522         AppObject.__init__(self, req, rset=rset, row=row, col=col)
   544         AppObject.__init__(self, req, rset=rset, row=row, col=col)
   523         self._cw_related_cache = {}
   545         self._cw_related_cache = {}
  1210         except AttributeError:
  1232         except AttributeError:
  1211             pass
  1233             pass
  1212 
  1234 
  1213     # raw edition utilities ###################################################
  1235     # raw edition utilities ###################################################
  1214 
  1236 
  1215     def set_attributes(self, **kwargs): # XXX cw_set_attributes
  1237     def cw_set(self, **kwargs):
       
  1238         """update this entity using given attributes / relation, working in the
       
  1239         same fashion as :meth:`cw_instantiate`.
       
  1240 
       
  1241         Example (in a shell session):
       
  1242 
       
  1243         >>> c = rql('Any X WHERE X is Company').get_entity(0, 0)
       
  1244         >>> p = rql('Any X WHERE X is Person').get_entity(0, 0)
       
  1245         >>> c.set(name=u'Logilab')
       
  1246         >>> p.set(firstname=u'John', lastname=u'Doe', works_for=c)
       
  1247 
       
  1248         You can also set relations where the entity has 'object' role by
       
  1249         prefixing the relation name by 'reverse_'.  Also, relation values may be
       
  1250         an entity or eid, a list of entities or eids, or None (meaning that all
       
  1251         relations of the given type from or to this object should be deleted).
       
  1252         """
  1216         _check_cw_unsafe(kwargs)
  1253         _check_cw_unsafe(kwargs)
  1217         assert kwargs
  1254         assert kwargs
  1218         assert self.cw_is_saved(), "should not call set_attributes while entity "\
  1255         assert self.cw_is_saved(), "should not call set_attributes while entity "\
  1219                "hasn't been saved yet"
  1256                "hasn't been saved yet"
  1220         relations = ['X %s %%(%s)s' % (key, key) for key in kwargs]
  1257         rql, qargs, pending_relations, attrcache = self._cw_build_entity_query(kwargs)
  1221         # and now update the database
  1258         if rql:
  1222         kwargs['x'] = self.eid
  1259             rql = 'SET ' + rql
  1223         self._cw.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
  1260             qargs['x'] = self.eid
  1224                          kwargs)
  1261             if ' WHERE ' in rql:
  1225         kwargs.pop('x')
  1262                 rql += ', X eid %(x)s'
       
  1263             else:
       
  1264                 rql += ' WHERE X eid %(x)s'
       
  1265             self._cw.execute(rql, qargs)
  1226         # update current local object _after_ the rql query to avoid
  1266         # update current local object _after_ the rql query to avoid
  1227         # interferences between the query execution itself and the cw_edited /
  1267         # interferences between the query execution itself and the cw_edited /
  1228         # skip_security machinery
  1268         # skip_security machinery
  1229         self.cw_attr_cache.update(kwargs)
  1269         self.cw_attr_cache.update(attrcache)
  1230 
  1270         self._cw_handle_pending_relations(self.eid, pending_relations, self._cw.execute)
  1231     def set_relations(self, **kwargs): # XXX cw_set_relations
  1271         # XXX update relation cache
  1232         """add relations to the given object. To set a relation where this entity
       
  1233         is the object of the relation, use 'reverse_'<relation> as argument name.
       
  1234 
       
  1235         Values may be an entity or eid, a list of entities or eids, or None
       
  1236         (meaning that all relations of the given type from or to this object
       
  1237         should be deleted).
       
  1238         """
       
  1239         # XXX update cache
       
  1240         _check_cw_unsafe(kwargs)
       
  1241         for attr, values in kwargs.iteritems():
       
  1242             if attr.startswith('reverse_'):
       
  1243                 restr = 'Y %s X' % attr[len('reverse_'):]
       
  1244             else:
       
  1245                 restr = 'X %s Y' % attr
       
  1246             if values is None:
       
  1247                 self._cw.execute('DELETE %s WHERE X eid %%(x)s' % restr,
       
  1248                                  {'x': self.eid})
       
  1249                 continue
       
  1250             if not isinstance(values, (tuple, list, set, frozenset)):
       
  1251                 values = (values,)
       
  1252             eids = []
       
  1253             for val in values:
       
  1254                 try:
       
  1255                     eids.append(str(val.eid))
       
  1256                 except AttributeError:
       
  1257                     try:
       
  1258                         eids.append(str(typed_eid(val)))
       
  1259                     except (ValueError, TypeError):
       
  1260                         raise Exception('expected an Entity or eid, got %s' % val)
       
  1261             self._cw.execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
       
  1262                     restr, ','.join(eids)), {'x': self.eid})
       
  1263 
  1272 
  1264     def cw_delete(self, **kwargs):
  1273     def cw_delete(self, **kwargs):
  1265         assert self.has_eid(), self.eid
  1274         assert self.has_eid(), self.eid
  1266         self._cw.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema,
  1275         self._cw.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema,
  1267                          {'x': self.eid}, **kwargs)
  1276                          {'x': self.eid}, **kwargs)
  1271     def _cw_clear_local_perm_cache(self, action):
  1280     def _cw_clear_local_perm_cache(self, action):
  1272         for rqlexpr in self.e_schema.get_rqlexprs(action):
  1281         for rqlexpr in self.e_schema.get_rqlexprs(action):
  1273             self._cw.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None)
  1282             self._cw.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None)
  1274 
  1283 
  1275     # deprecated stuff #########################################################
  1284     # deprecated stuff #########################################################
       
  1285 
       
  1286     @deprecated('[3.15] use cw_set() instead')
       
  1287     def set_attributes(self, **kwargs): # XXX cw_set_attributes
       
  1288         self.cw_set(**kwargs)
       
  1289 
       
  1290     @deprecated('[3.15] use cw_set() instead')
       
  1291     def set_relations(self, **kwargs): # XXX cw_set_relations
       
  1292         """add relations to the given object. To set a relation where this entity
       
  1293         is the object of the relation, use 'reverse_'<relation> as argument name.
       
  1294 
       
  1295         Values may be an entity or eid, a list of entities or eids, or None
       
  1296         (meaning that all relations of the given type from or to this object
       
  1297         should be deleted).
       
  1298         """
       
  1299         self.cw_set(**kwargs)
  1276 
  1300 
  1277     @deprecated('[3.13] use entity.cw_clear_all_caches()')
  1301     @deprecated('[3.13] use entity.cw_clear_all_caches()')
  1278     def clear_all_caches(self):
  1302     def clear_all_caches(self):
  1279         return self.cw_clear_all_caches()
  1303         return self.cw_clear_all_caches()
  1280 
  1304