"""base application's entities class implementation: `AnyEntity`:organization: Logilab:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr"""__docformat__="restructuredtext en"fromwarningsimportwarnfromlogilab.common.deprecationimportdeprecated_functionfromlogilab.common.decoratorsimportcachedfromcubicwebimportUnauthorized,typed_eidfromcubicweb.common.utilsimportdump_classfromcubicweb.common.entityimportEntityfromcubicweb.schemaimportFormatConstraintfromcubicweb.interfacesimportIBreadCrumbs,IFeedclassAnyEntity(Entity):"""an entity instance has e_schema automagically set on the class and instances have access to their issuing cursor """id='Any'__rtags__={'is':('generated','link'),'is_instance_of':('generated','link'),'identity':('generated','link'),# use primary and not generated for eid since it has to be an hidden# field in edition('eid','*','subject'):'primary',('creation_date','*','subject'):'generated',('modification_date','*','subject'):'generated',('has_text','*','subject'):'generated',('require_permission','*','subject'):('generated','link'),('owned_by','*','subject'):('generated','link'),('created_by','*','subject'):('generated','link'),('wf_info_for','*','subject'):('generated','link'),('wf_info_for','*','object'):('generated','link'),('description','*','subject'):'secondary',# XXX should be moved in their respective cubes('filed_under','*','subject'):('generic','link'),('filed_under','*','object'):('generic','create'),# generated since there is a componant to handle comments('comments','*','subject'):('generated','link'),('comments','*','object'):('generated','link'),}__implements__=(IBreadCrumbs,IFeed)@classmethoddefselected(cls,etype):"""the special Any entity is used as the default factory, so the actual class has to be constructed at selection time once we have an actual entity'type """ifcls.id==etype:returnclsusercls=dump_class(cls,etype)usercls.id=etypeusercls.__initialize__()returnuserclsfetch_attrs=('modification_date',)@classmethoddeffetch_order(cls,attr,var):"""class method used to control sort order when multiple entities of this type are fetched """returncls.fetch_unrelated_order(attr,var)@classmethoddeffetch_unrelated_order(cls,attr,var):"""class method used to control sort order when multiple entities of this type are fetched to use in edition (eg propose them to create a new relation on an edited entity). """ifattr=='modification_date':return'%s DESC'%varreturnNone@classmethoddef__initialize__(cls):super(ANYENTITY,cls).__initialize__()# XXXeschema=cls.e_schemaeschema.format_fields={}# set a default_ATTR method for rich text format fieldsforattr,formatattrineschema.rich_text_fields():ifnothasattr(cls,'default_%s'%formatattr):setattr(cls,'default_%s'%formatattr,cls._default_format)eschema.format_fields[formatattr]=attrdef_default_format(self):returnself.req.property_value('ui.default-text-format')defuse_fckeditor(self,attr):"""return True if fckeditor should be used to edit entity's attribute named `attr`, according to user preferences """req=self.reqifreq.property_value('ui.fckeditor')andself.has_format(attr):ifself.has_eid()or'%s_format'%attrinself:returnself.format(attr)=='text/html'returnreq.property_value('ui.default-text-format')=='text/html'returnFalse# meta data api ###########################################################defdc_title(self):"""return a suitable *unicode* title for this entity"""forrschema,attrschemainself.e_schema.attribute_definitions():ifrschema.meta:continuevalue=self.get_value(rschema.type)ifvalue:# make the value printable (dates, floats, bytes, etc.)returnself.printable_value(rschema.type,value,attrschema.type,format='text/plain')returnu'%s #%s'%(self.dc_type(),self.eid)defdc_long_title(self):"""return a more detailled title for this entity"""returnself.dc_title()defdc_description(self,format='text/plain'):"""return a suitable description for this entity"""ifself.e_schema.has_subject_relation('description'):returnself.printable_value('description',format=format)returnu''defdc_authors(self):"""return a suitable description for the author(s) of the entity"""try:return', '.join(u.name()foruinself.owned_by)exceptUnauthorized:returnu''defdc_creator(self):"""return a suitable description for the creator of the entity"""ifself.creator:returnself.creator.name()returnu''defdc_date(self,date_format=None):# XXX default to ISO 8601 ?"""return latest modification date of this entity"""returnself.format_date(self.modification_date,date_format=date_format)defdc_type(self,form=''):"""return the display name for the type of this entity (translated)"""returnself.e_schema.display_name(self.req,form)display_name=deprecated_function(dc_type)# require agueol > 0.8.1, asteretud > 0.10.0 for removaldefdc_language(self):"""return language used by this entity (translated)"""# check if entities has internationalizable attributes# XXX one is enough or check if all String attributes are internationalizable?forrschema,attrschemainself.e_schema.attribute_definitions():ifrschema.rproperty(self.e_schema,attrschema,'internationalizable'):returnself.req._(self.req.user.property_value('ui.language'))returnself.req._(self.vreg.property_value('ui.language'))@propertydefcreator(self):"""return the EUser entity which has created this entity, or None if unknown or if the curent user doesn't has access to this euser """try:returnself.created_by[0]except(Unauthorized,IndexError):returnNonedefbreadcrumbs(self,view=None,recurs=False):path=[self]ifhasattr(self,'parent'):parent=self.parent()ifparentisnotNone:try:path=parent.breadcrumbs(view,True)+[self]exceptTypeError:warn("breadcrumbs method's now takes two arguments ""(view=None, recurs=False), please update",DeprecationWarning)path=parent.breadcrumbs(view)+[self]ifnotrecurs:ifviewisNone:if'vtitle'inself.req.form:# embeding for instancepath.append(self.req.form['vtitle'])elifview.id!='primary'andhasattr(view,'title'):path.append(self.req._(view.title))returnpath## IFeed interface ########################################################defrss_feed_url(self):returnself.absolute_url(vid='rss')# abstractions making the whole things (well, some at least) working ######@classmethoddefget_widget(cls,rschema,x='subject'):"""return a widget to view or edit a relation notice that when the relation support multiple target types, the widget is necessarily the same for all those types """# let ImportError propage if web par isn't availablefromcubicweb.web.widgetsimportwidgetifisinstance(rschema,basestring):rschema=cls.schema.rschema(rschema)ifx=='subject':tschema=rschema.objects(cls.e_schema)[0]wdg=widget(cls.vreg,cls,rschema,tschema,'subject')else:tschema=rschema.subjects(cls.e_schema)[0]wdg=widget(cls.vreg,tschema,rschema,cls,'object')returnwdgdefsortvalue(self,rtype=None):"""return a value which can be used to sort this entity or given entity's attribute """ifrtypeisNone:returnself.dc_title().lower()value=self.get_value(rtype)# do not restrict to `unicode` because Bytes will return a `str` valueifisinstance(value,basestring):returnself.printable_value(rtype,format='text/plain').lower()returnvaluedefafter_deletion_path(self):"""return (path, parameters) which should be used as redirect information when this entity is being deleted """returnstr(self.e_schema).lower(),{}defadd_related_schemas(self):"""this is actually used ui method to generate 'addrelated' actions from the schema. If you're using explicit 'addrelated' actions for an entity types, you should probably overrides this method to return an empty list else you may get some unexpected actions. """req=self.reqeschema=self.e_schemaforrole,rschemasin(('subject',eschema.subject_relations()),('object',eschema.object_relations())):forrschemainrschemas:ifrschema.is_final():continue# check the relation can be added as wellifrole=='subject'andnotrschema.has_perm(req,'add',fromeid=self.eid):continueifrole=='object'andnotrschema.has_perm(req,'add',toeid=self.eid):continue# check the target types can be added as wellforteschemainrschema.targets(eschema,role):ifnotself.relation_mode(rschema,teschema,role)=='create':continueifteschema.has_local_role('add')orteschema.has_perm(req,'add'):yieldrschema,teschema,roledefrelation_mode(self,rtype,targettype,role='subject'):"""return a string telling if the given relation is usually created to a new entity ('create' mode) or to an existant entity ('link' mode) """returnself.rtags.get_mode(rtype,targettype,role)# edition helper functions ################################################defrelations_by_category(self,categories=None,permission=None):ifcategoriesisnotNone:ifnotisinstance(categories,(list,tuple,set,frozenset)):categories=(categories,)ifnotisinstance(categories,(set,frozenset)):categories=frozenset(categories)eschema,rtags=self.e_schema,self.rtagsifself.has_eid():eid=self.eidelse:eid=Noneforrschema,targetschemas,roleineschema.relation_definitions(True):ifrschemain('identity','has_text'):continue# check category first, potentially lower cost than checking# permission which may imply rql queriesifcategoriesisnotNone:targetschemas=[tschemafortschemaintargetschemasifrtags.get_tags(rschema.type,tschema.type,role).intersection(categories)]ifnottargetschemas:continuetags=rtags.get_tags(rschema.type,role=role)ifpermissionisnotNone:# tag allowing to hijack the permission machinery when# permission is not verifiable until the entity is actually# created...ifeidisNoneand('%s_on_new'%permission)intags:yield(rschema,targetschemas,role)continueifrschema.is_final():ifnotrschema.has_perm(self.req,permission,eid):continueelifrole=='subject':ifnot((eidisNoneandrschema.has_local_role(permission))orrschema.has_perm(self.req,permission,fromeid=eid)):continue# on relation with cardinality 1 or ?, we need delete perm as well# if the relation is already setif(permission=='add'andrschema.cardinality(eschema,targetschemas[0],role)in'1?'andself.has_eid()andself.related(rschema.type,role)andnotrschema.has_perm(self.req,'delete',fromeid=eid,toeid=self.related(rschema.type,role)[0][0])):continueelifrole=='object':ifnot((eidisNoneandrschema.has_local_role(permission))orrschema.has_perm(self.req,permission,toeid=eid)):continue# on relation with cardinality 1 or ?, we need delete perm as well# if the relation is already setif(permission=='add'andrschema.cardinality(targetschemas[0],eschema,role)in'1?'andself.has_eid()andself.related(rschema.type,role)andnotrschema.has_perm(self.req,'delete',toeid=eid,fromeid=self.related(rschema.type,role)[0][0])):continueyield(rschema,targetschemas,role)defsrelations_by_category(self,categories=None,permission=None):result=[]forrschema,ttypes,targetinself.relations_by_category(categories,permission):ifrschema.is_final():continueresult.append((rschema.display_name(self.req,target),rschema,target))returnsorted(result)defattribute_values(self,attrname):ifself.has_eid()orattrnameinself:try:values=self[attrname]exceptKeyError:values=getattr(self,attrname)# actual relation return a list of entitiesifisinstance(values,list):return[v.eidforvinvalues]return(values,)# the entity is being created, try to find default value for# this attributetry:values=self.req.form[attrname]exceptKeyError:try:values=self[attrname]# copyingexceptKeyError:values=getattr(self,'default_%s'%attrname,self.e_schema.default(attrname))ifcallable(values):values=values()ifvaluesisNone:values=()elifnotisinstance(values,(list,tuple)):values=(values,)returnvaluesdeflinked_to(self,rtype,target,remove=True):"""if entity should be linked to another using __linkto form param for the given relation/target, return eids of related entities This method is consuming matching link-to information from form params if `remove` is True (by default). """try:returnself.__linkto[(rtype,target)]exceptAttributeError:self.__linkto={}exceptKeyError:passlinktos=list(self.req.list_form_param('__linkto'))linkedto=[]forlinktoinlinktos[:]:ltrtype,eid,lttarget=linkto.split(':')ifrtype==ltrtypeandtarget==lttarget:# delete __linkto from form param to avoid it being added as# hidden inputifremove:linktos.remove(linkto)self.req.form['__linkto']=linktoslinkedto.append(typed_eid(eid))self.__linkto[(rtype,target)]=linkedtoreturnlinkedtodefpre_web_edit(self):"""callback called by the web editcontroller when an entity will be created/modified, to let a chance to do some entity specific stuff. Do nothing by default. """pass# server side helpers #####################################################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 """return()# XXX: store a reference to the AnyEntity class since it is hijacked in goa# configuration and we need the actual reference to avoid infinite loops# in mroANYENTITY=AnyEntitydeffetch_config(fetchattrs,mainattr=None,pclass=AnyEntity,order='ASC'):ifpclassisANYENTITY:pclass=AnyEntity# AnyEntity and ANYENTITY may be different classesifpclassisnotNone:fetchattrs+=pclass.fetch_attrsifmainattrisNone:mainattr=fetchattrs[0]@classmethoddeffetch_order(cls,attr,var):ifattr==mainattr:return'%s%s'%(var,order)returnNonereturnfetchattrs,fetch_order