# HG changeset patch # User Sylvain Thénault # Date 1250093183 -7200 # Node ID 971d7c545505ba084a2c311dcf922712b8fe40a4 # Parent 34dfdbefcfd14fb71a9b5d2959d2adb860e0d0a8# Parent f395115070c11c75f79c3f4cb6fe3832a036d61b merge diff -r 34dfdbefcfd1 -r 971d7c545505 common/mixins.py --- a/common/mixins.py Wed Aug 12 14:05:36 2009 +0200 +++ b/common/mixins.py Wed Aug 12 18:06:23 2009 +0200 @@ -168,7 +168,7 @@ @property def state(self): try: - return self.related('in_state', entities=True)[0].get_value('name') + return self.in_state[0].name except IndexError: self.warning('entity %s has no state', self) return None @@ -259,10 +259,10 @@ __implements__ = (IEmailable,) def get_email(self): - emails = self.related('primary_email', 'subject', entities=True) or \ - self.related('use_email', 'subject', entities=True) - if emails: - return emails[0].get_value('address') + if getattr(self, 'primary_email', None): + return self.primary_email[0].address + if getattr(self, 'use_email', None): + return self.use_email[0].address return None @classmethod @@ -283,8 +283,7 @@ NOTE: the dictionary keys should match the list returned by the `allowed_massmail_keys` method. """ - return dict( (attr, self.cwgetattr(attr)) - for attr in self.allowed_massmail_keys() ) + return dict( (attr, getattr(self, attr)) for attr in self.allowed_massmail_keys() ) diff -r 34dfdbefcfd1 -r 971d7c545505 entities/authobjs.py --- a/entities/authobjs.py Wed Aug 12 14:05:36 2009 +0200 +++ b/entities/authobjs.py Wed Aug 12 18:06:23 2009 +0200 @@ -44,7 +44,7 @@ try: return self._groups except AttributeError: - self._groups = set(g.cwdb.name for g in self.cwdb.in_group) + self._groups = set(g.name for g in self.in_group) return self._groups @property @@ -52,8 +52,7 @@ try: return self._properties except AttributeError: - self._properties = dict((p.cwdb.pkey, p.cwdb.value) - for p in self.cwdb.reverse_for_user) + self._properties = dict((p.pkey, p.value) for p in self.reverse_for_user) return self._properties def property_value(self, key): @@ -64,8 +63,7 @@ except KeyError: pass except ValueError: - self.warning('incorrect value for property %s of user %s', key, - self.cwdb.login) + self.warning('incorrect value for eproperty %s of user %s', key, self.login) return self.vreg.property_value(key) def matching_groups(self, groups): @@ -124,17 +122,16 @@ def name(self): """construct a name using firstname / surname or login if not defined""" - surname = self.get_value('surname') - firstname = self.get_value('firstname') - if firstname and surname: + + if self.firstname and self.surname: return self.req._('%(firstname)s %(surname)s') % { - 'firstname': firstname, 'surname' : surname} - if firstname or surname: - return surname or firstname - return self.get_value('login') + 'firstname': self.firstname, 'surname' : self.surname} + if self.firstname: + return self.firstname + return self.login def dc_title(self): - return self.get_value('login') + return self.login dc_long_title = name diff -r 34dfdbefcfd1 -r 971d7c545505 entity.py --- a/entity.py Wed Aug 12 14:05:36 2009 +0200 +++ b/entity.py Wed Aug 12 18:06:23 2009 +0200 @@ -132,20 +132,14 @@ return super(_metaentity, mcs).__new__(mcs, name, bases, classdict) -_CWDB_CLASSES = {} - -def cwdb___init__(self, entity): - self.entity = entity +class Entity(AppObject, dict): + """an entity instance has e_schema automagically set on + the class and instances has access to their issuing cursor. -class Entity(AppObject, dict): - """an entity instance has e_schema automagically set on the class. - - Also its special cwdb attribute provides access to persistent attributes and - relation using properties which are set for each attribute and relation - according to entity's type schema. - - Beware that among attributes, 'eid' is *NEITHER* stored in the dict - containment (which acts as a cache for other attributes dynamically fetched) + A property is set for each attribute and relation on each entity's type + class. Becare that among attributes, 'eid' is *NEITHER* stored in the + dict containment (which acts as a cache for other attributes dynamically + fetched) :type e_schema: `cubicweb.schema.EntitySchema` :ivar e_schema: the entity's schema @@ -183,30 +177,24 @@ etype = cls.id assert etype != 'Any', etype cls.e_schema = eschema = cls.schema.eschema(etype) - cwdbclsdict = {'__init__': cwdb___init__} for rschema, _ in eschema.attribute_definitions(): - rtype = rschema.type - if rtype == 'eid': + if rschema.type == 'eid': continue - setattr(cls, rtype, DeprecatedAttribute(rtype)) - cwdbclsdict[rtype] = Attribute(rtype) + setattr(cls, rschema.type, Attribute(rschema.type)) mixins = [] - for rschema, _, role in eschema.relation_definitions(): - if (rschema, role) in MI_REL_TRIGGERS: - mixin = MI_REL_TRIGGERS[(rschema, role)] + for rschema, _, x in eschema.relation_definitions(): + if (rschema, x) in MI_REL_TRIGGERS: + mixin = MI_REL_TRIGGERS[(rschema, x)] if not (issubclass(cls, mixin) or mixin in mixins): # already mixed ? mixins.append(mixin) for iface in getattr(mixin, '__implements__', ()): if not interface.implements(cls, iface): interface.extend(cls, iface) - rtype = rschema.type - if role == 'object': - attr = 'reverse_%s' % rtype + if x == 'subject': + setattr(cls, rschema.type, SubjectRelation(rschema)) else: - attr = rtype - setattr(cls, attr, DeprecatedRelation(rtype, role)) - cwdbclsdict[attr] = Relation(rtype, role) - _CWDB_CLASSES[etype] = type(etype + 'CWDB', (object,), cwdbclsdict) + attr = 'reverse_%s' % rschema.type + setattr(cls, attr, ObjectRelation(rschema)) if mixins: cls.__bases__ = tuple(mixins + [p for p in cls.__bases__ if not p is object]) cls.debug('plugged %s mixins on %s', mixins, etype) @@ -311,8 +299,6 @@ else: self.eid = None self._is_saved = True - if self.id != 'Any': - self.cwdb = _CWDB_CLASSES[self.id](self) def __repr__(self): return '' % ( @@ -324,15 +310,6 @@ def __hash__(self): return id(self) - def cwgetattr(self, attr, default=_marker): - """return attribute from either self.cwdb or self""" - try: - return getattr(self.cwdb, attr) - except AttributeError: - if default is _marker: - return getattr(self, attr) - return getattr(self, attr, default) - def pre_add_hook(self): """hook called by the repository before doing anything to add the entity (before_add entity hooks have not been called yet). This give the @@ -415,7 +392,7 @@ etype = str(self.e_schema) path = etype.lower() if mainattr != 'eid': - value = getattr(self.cwdb, mainattr) + value = getattr(self, mainattr) if value is None or unicode(value) == u'': mainattr = 'eid' path += '/eid' @@ -436,7 +413,7 @@ def attr_metadata(self, attr, metadata): """return a metadata for an attribute (None if unspecified)""" - value = self.cwgetattr('%s_%s' % (attr, metadata), None) + value = getattr(self, '%s_%s' % (attr, metadata), None) if value is None and metadata == 'encoding': value = self.vreg.property_value('ui.encoding') return value @@ -448,17 +425,14 @@ """ attr = str(attr) if value is _marker: - value = self.cwgetattr(attr) + value = getattr(self, attr) if isinstance(value, basestring): value = value.strip() if value is None or value == '': # don't use "not", 0 is an acceptable value return u'' if attrtype is None: attrtype = self.e_schema.destination(attr) - try: - props = self.e_schema.rproperties(attr) - except KeyError: - props = {} + props = self.e_schema.rproperties(attr) if attrtype == 'String': # internalinalized *and* formatted string such as schema # description... @@ -499,20 +473,13 @@ assert self.has_eid() execute = self.req.execute for rschema in self.e_schema.subject_relations(): - # skip final, meta or composite relation - if rschema.is_final() or rschema.meta or self.e_schema.subjrproperty(rschema, 'composite'): + if rschema.is_final() or rschema.meta: continue - # skip relation with card in ?1 else we either change the copied - # object (inlined relation) or inserting some inconsistency - if self.e_schema.subjrproperty(rschema, 'cardinality')[1] in '?1': + # skip already defined relations + if getattr(self, rschema.type): continue - # skip if we're told to do so if rschema.type in self.skip_copy_for: continue - # skip already defined relations - if self.related(rschema.type, 'subject'): - continue - # special case for in_state if rschema.type == 'in_state': # if the workflow is defining an initial state (XXX AND we are # not in the managers group? not done to be more consistent) @@ -520,26 +487,32 @@ if execute('Any S WHERE S state_of ET, ET initial_state S,' 'ET name %(etype)s', {'etype': str(self.e_schema)}): continue + # skip composite relation + if self.e_schema.subjrproperty(rschema, 'composite'): + continue + # skip relation with card in ?1 else we either change the copied + # object (inlined relation) or inserting some inconsistency + if self.e_schema.subjrproperty(rschema, 'cardinality')[1] in '?1': + continue rql = 'SET X %s V WHERE X eid %%(x)s, Y eid %%(y)s, Y %s V' % ( - rschema, rschema) + rschema.type, rschema.type) execute(rql, {'x': self.eid, 'y': ceid}, ('x', 'y')) self.clear_related_cache(rschema.type, 'subject') for rschema in self.e_schema.object_relations(): - # skip meta or composite - if rschema.meta or self.e_schema.objrproperty(rschema, 'composite'): + if rschema.meta: + continue + # skip already defined relations + if getattr(self, 'reverse_%s' % rschema.type): + continue + # skip composite relation + if self.e_schema.objrproperty(rschema, 'composite'): continue # skip relation with card in ?1 else we either change the copied # object (inlined relation) or inserting some inconsistency if self.e_schema.objrproperty(rschema, 'cardinality')[0] in '?1': continue - # skip if we're told to do so - if rschema.type in self.skip_copy_for: - continue - # skip already defined relations - if self.related(rschema.type, 'object'): - continue rql = 'SET V %s X WHERE X eid %%(x)s, Y eid %%(y)s, V %s Y' % ( - rschema, rschema) + rschema.type, rschema.type) execute(rql, {'x': self.eid, 'y': ceid}, ('x', 'y')) self.clear_related_cache(rschema.type, 'object') @@ -904,9 +877,9 @@ yielded = False for rschema, target in containers: if target == 'object': - targets = self.related(rschema.type, 'subject', entities=True) + targets = getattr(self, rschema.type) else: - targets = self.related(rschema.type, 'object', entities=True) + targets = getattr(self, 'reverse_%s' % rschema) for entity in targets: if entity.eid in _done: continue @@ -942,73 +915,61 @@ words += tokenize(value) for rschema, role in self.e_schema.fulltext_relations(): - for entity in self.related(rschema.type, role, entities=True): - words += entity.get_words() + if role == 'subject': + for entity in getattr(self, rschema.type): + words += entity.get_words() + else: # if role == 'object': + for entity in getattr(self, 'reverse_%s' % rschema.type): + words += entity.get_words() return words # attribute and relation descriptors ########################################## - class Attribute(object): """descriptor that controls schema attribute access""" - def __init__(self, rtype): - assert rtype != 'eid' - self._rtype = rtype + def __init__(self, attrname): + assert attrname != 'eid' + self._attrname = attrname + + def __get__(self, eobj, eclass): + if eobj is None: + return self + return eobj.get_value(self._attrname) + + def __set__(self, eobj, value): + # XXX bw compat + # would be better to generate UPDATE queries than the current behaviour + eobj.warning("deprecated usage, don't use 'entity.attr = val' notation)") + eobj[self._attrname] = value - def __get__(self, cwdbobj, eclass): - if cwdbobj is None: - return self - return cwdbobj.entity.get_value(self._rtype) + +class Relation(object): + """descriptor that controls schema relation access""" + _role = None # for pylint + + def __init__(self, rschema): + self._rschema = rschema + self._rtype = rschema.type + + def __get__(self, eobj, eclass): + if eobj is None: + raise AttributeError('%s cannot be only be accessed from instances' + % self._rtype) + return eobj.related(self._rtype, self._role, entities=True) def __set__(self, eobj, value): raise NotImplementedError -class Relation(Attribute): +class SubjectRelation(Relation): """descriptor that controls schema relation access""" - - def __init__(self, rtype, role): - super(Relation, self).__init__(rtype) - self._role = role - - def __get__(self, cwdbobj, eclass): - if cwdbobj is None: - raise AttributeError('%s cannot be only be accessed from instances' - % self._rtype) - return cwdbobj.entity.related(self._rtype, self._role, entities=True) - - -class DeprecatedAttribute(Attribute): - """descriptor that controls schema attribute access""" + _role = 'subject' - def __get__(self, eobj, eclass): - rtype = self._rtype - warn('entity.%s is deprecated, use entity.cwdb.%s' % (rtype, rtype), - DeprecationWarning, stacklevel=2) - if eobj is None: - return self - return eobj.get_value(self._rtype) - - def __set__(self, eobj, value): - # XXX bw compat - # would be better to generate UPDATE queries than the current behaviour - eobj.warning("deprecated usage, don't use 'entity.attr = val' notation)") - eobj[self._rtype] = value - - -class DeprecatedRelation(Relation): +class ObjectRelation(Relation): """descriptor that controls schema relation access""" - - def __get__(self, eobj, eclass): - rtype = self._rtype - warn('entity.[reverse_]%s is deprecated, use entity.cwdb.[reverse_]%s' - % (rtype, rtype), DeprecationWarning, stacklevel=2) - if eobj is None: - raise AttributeError('%s cannot be only be accessed from instances' - % self._rtype) - return eobj.related(rtype, self._role, entities=True) + _role = 'object' from logging import getLogger from cubicweb import set_log_methods diff -r 34dfdbefcfd1 -r 971d7c545505 server/hooks.py --- a/server/hooks.py Wed Aug 12 14:05:36 2009 +0200 +++ b/server/hooks.py Wed Aug 12 18:06:23 2009 +0200 @@ -67,7 +67,7 @@ if self.entity.eid in session.transaction_data.get('pendingeids', ()): # entity have been created and deleted in the same transaction return - if not self.entity.related('created_by'): + if not self.entity.created_by: session.add_relation(self.entity.eid, 'created_by', session.user.eid) diff -r 34dfdbefcfd1 -r 971d7c545505 server/repository.py --- a/server/repository.py Wed Aug 12 14:05:36 2009 +0200 +++ b/server/repository.py Wed Aug 12 18:06:23 2009 +0200 @@ -687,7 +687,7 @@ if props is not None: self.set_session_props(sessionid, props) user = session.user - return user.eid, user.cwdb.login, user.groups, user.properties + return user.eid, user.login, user.groups, user.properties def set_session_props(self, sessionid, props): """this method should be used by client to: diff -r 34dfdbefcfd1 -r 971d7c545505 server/session.py --- a/server/session.py Wed Aug 12 14:05:36 2009 +0200 +++ b/server/session.py Wed Aug 12 18:06:23 2009 +0200 @@ -48,7 +48,7 @@ def __init__(self, user, repo, cnxprops=None, _id=None): super(Session, self).__init__(repo.vreg) - self.id = _id or make_uid(user.cwdb.login.encode('UTF8')) + self.id = _id or make_uid(user.login.encode('UTF8')) cnxprops = cnxprops or ConnectionProperties('inmemory') self.user = user self.repo = repo diff -r 34dfdbefcfd1 -r 971d7c545505 test/unittest_rset.py --- a/test/unittest_rset.py Wed Aug 12 14:05:36 2009 +0200 +++ b/test/unittest_rset.py Wed Aug 12 18:06:23 2009 +0200 @@ -117,7 +117,7 @@ rs.req = self.request() rs.vreg = self.vreg def test_filter(entity): - return entity.cwdb.login != 'nico' + return entity.login != 'nico' rs2 = rs.filtered_rset(test_filter) self.assertEquals(len(rs2), 2) @@ -271,11 +271,11 @@ self.assertEquals(pprelcachedict(e._related_cache), [('created_by_subject', [5])]) # first level of recursion - u = e.cwdb.created_by[0] + u = e.created_by[0] self.assertEquals(u['login'], 'admin') self.assertRaises(KeyError, u.__getitem__, 'firstname') # second level of recursion - s = u.cwdb.in_state[0] + s = u.in_state[0] self.assertEquals(s['name'], 'activated') self.assertRaises(KeyError, s.__getitem__, 'description')