two convenience files
* .hgignore with good default values
* cubicweb-ctl.bat to launch things under win32
"""mixins of entity/views organized somewhat in a graph or tree structure:organization: Logilab:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses"""__docformat__="restructuredtext en"fromlogilab.common.deprecationimportdeprecatedfromlogilab.common.decoratorsimportcachedfromcubicwebimporttyped_eidfromcubicweb.selectorsimportimplementsfromcubicweb.interfacesimportIEmailable,ITreeclassTreeMixIn(object):"""base tree-mixin providing the tree interface This mixin has to be inherited explicitly and configured using the tree_attribute, parent_target and children_target class attribute to benefit from this default implementation """tree_attribute=None# XXX misnamedparent_target='subject'children_target='object'defdifferent_type_children(self,entities=True):"""return children entities of different type as this entity. according to the `entities` parameter, return entity objects or the equivalent result set """res=self.related(self.tree_attribute,self.children_target,entities=entities)ifentities:return[eforeinresife.e_schema!=self.e_schema]returnres.filtered_rset(lambdax:x.e_schema!=self.e_schema,self.col)defsame_type_children(self,entities=True):"""return children entities of the same type as this entity. according to the `entities` parameter, return entity objects or the equivalent result set """res=self.related(self.tree_attribute,self.children_target,entities=entities)ifentities:return[eforeinresife.e_schema==self.e_schema]returnres.filtered_rset(lambdax:x.e_schema==self.e_schema,self.col)defiterchildren(self,_done=None):if_doneisNone:_done=set()forchildinself.children():ifchild.eidin_done:self.error('loop in %s tree',self.id.lower())continueyieldchild_done.add(child.eid)defprefixiter(self,_done=None):if_doneisNone:_done=set()ifself.eidin_done:returnyieldself_done.add(self.eid)forchildinself.iterchildren(_done):try:forentityinchild.prefixiter(_done):yieldentityexceptAttributeError:pass@cacheddefpath(self):"""returns the list of eids from the root object to this object"""path=[]parent=selfwhileparent:ifparent.eidinpath:self.error('loop in %s tree',self.id.lower())breakpath.append(parent.eid)try:# check we are not leaving the treeif(parent.tree_attribute!=self.tree_attributeorparent.parent_target!=self.parent_target):breakparent=parent.parent()exceptAttributeError:breakpath.reverse()returnpathdefiterparents(self):def_uptoroot(self):curr=selfwhileTrue:curr=curr.parent()ifcurrisNone:breakyieldcurrreturn_uptoroot(self)defnotification_references(self,view):"""used to control References field of email send on notification for this entity. `view` is the notification view. Should return a list of eids which can be used to generate message ids of previously sent email """returnself.path()[:-1]## ITree interface ########################################################defparent(self):"""return the parent entity if any, else None (e.g. if we are on the root """try:returnself.related(self.tree_attribute,self.parent_target,entities=True)[0]except(KeyError,IndexError):returnNonedefchildren(self,entities=True,sametype=False):"""return children entities according to the `entities` parameter, return entity objects or the equivalent result set """ifsametype:returnself.same_type_children(entities)else:returnself.related(self.tree_attribute,self.children_target,entities=entities)defchildren_rql(self):returnself.related_rql(self.tree_attribute,self.children_target)def__iter__(self):returnself.iterchildren()defis_leaf(self):returnlen(self.children())==0defis_root(self):returnself.parent()isNonedefroot(self):"""return the root object"""returnself.req.entity_from_eid(self.path()[0])classEmailableMixIn(object):"""base mixin providing the default get_email() method used by the massmailing view NOTE: The default implementation is based on the primary_email / use_email scheme """__implements__=(IEmailable,)defget_email(self):ifgetattr(self,'primary_email',None):returnself.primary_email[0].addressifgetattr(self,'use_email',None):returnself.use_email[0].addressreturnNone@classmethoddefallowed_massmail_keys(cls):"""returns a set of allowed email substitution keys The default is to return the entity's attribute list but an entity class might override this method to allow extra keys. For instance, the Person class might want to return a `companyname` key. """returnset(rs.typeforrs,_incls.e_schema.attribute_definitions())defas_email_context(self):"""returns the dictionary as used by the sendmail controller to build email bodies. NOTE: the dictionary keys should match the list returned by the `allowed_massmail_keys` method. """returndict((attr,getattr(self,attr))forattrinself.allowed_massmail_keys())MI_REL_TRIGGERS={('primary_email','subject'):EmailableMixIn,('use_email','subject'):EmailableMixIn,}def_done_init(done,view,row,col):"""handle an infinite recursion safety belt"""ifdoneisNone:done=set()entity=view.entity(row,col)ifentity.eidindone:msg=entity.req._('loop in %(rel)s relation (%(eid)s)')%{'rel':entity.tree_attribute,'eid':entity.eid}returnNone,msgdone.add(entity.eid)returndone,entityclassTreeViewMixIn(object):"""a recursive tree view"""id='tree'item_vid='treeitem'__select__=implements(ITree)defcall(self,done=None,**kwargs):ifdoneisNone:done=set()super(TreeViewMixIn,self).call(done=done,**kwargs)defcell_call(self,row,col=0,vid=None,done=None,**kwargs):done,entity=_done_init(done,self,row,col)ifdoneisNone:# entity is actually an error messageself.w(u'<li class="badcontent">%s</li>'%entity)returnself.open_item(entity)entity.view(vidorself.item_vid,w=self.w,**kwargs)relatedrset=entity.children(entities=False)self.wview(self.id,relatedrset,'null',done=done,**kwargs)self.close_item(entity)defopen_item(self,entity):self.w(u'<li class="%s">\n'%entity.id.lower())defclose_item(self,entity):self.w(u'</li>\n')classTreePathMixIn(object):"""a recursive path view"""id='path'item_vid='oneline'separator=u' > 'defcall(self,**kwargs):self.w(u'<div class="pathbar">')super(TreePathMixIn,self).call(**kwargs)self.w(u'</div>')defcell_call(self,row,col=0,vid=None,done=None,**kwargs):done,entity=_done_init(done,self,row,col)ifdoneisNone:# entity is actually an error messageself.w(u'<span class="badcontent">%s</span>'%entity)returnparent=entity.parent()ifparent:parent.view(self.id,w=self.w,done=done)self.w(self.separator)entity.view(vidorself.item_vid,w=self.w)classProgressMixIn(object):"""provide default implementations for IProgress interface methods"""# This is an adapter isn't it ?@propertydefcost(self):returnself.progress_info()['estimated']@propertydefrevised_cost(self):returnself.progress_info().get('estimatedcorrected',self.cost)@propertydefdone(self):returnself.progress_info()['done']@propertydeftodo(self):returnself.progress_info()['todo']@cacheddefprogress_info(self):raiseNotImplementedError()deffinished(self):returnnotself.in_progress()defin_progress(self):raiseNotImplementedError()defprogress(self):try:return100.*self.done/self.revised_costexceptZeroDivisionError:# total cost is 0 : if everything was estimated, task is completedifself.progress_info().get('notestimated'):return0.return100