"""The edit controller, handling form submitting.: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"fromdecimalimportDecimalfromrql.utilsimportrqlvar_makerfromcubicwebimportBinary,ValidationError,typed_eidfromcubicweb.webimportINTERNAL_FIELD_VALUE,RequestError,NothingToEdit,ProcessFormErrorfromcubicweb.web.controllerimportparse_relations_descrfromcubicweb.web.views.basecontrollersimportViewControllerclassToDoLater(Exception):"""exception used in the edit controller to indicate that a relation can't be handled right now and have to be handled later """classRqlQuery(object):def__init__(self):self.edited=[]self.restrictions=[]self.kwargs={}definsert_query(self,etype):ifself.edited:rql='INSERT %s X: %s'%(etype,','.join(self.edited))else:rql='INSERT %s X'%etypeifself.restrictions:rql+=' WHERE %s'%','.join(self.restrictions)returnrqldefupdate_query(self,eid):varmaker=rqlvar_maker()var=varmaker.next()whilevarinself.kwargs:var=varmaker.next()rql='SET %s WHERE X eid %%(%s)s'%(','.join(self.edited),var)ifself.restrictions:rql+=', %s'%','.join(self.restrictions)self.kwargs[var]=eidreturnrqlclassEditController(ViewController):__regid__='edit'defpublish(self,rset=None):"""edit / create / copy / delete entity / relations"""forkeyinself._cw.form:# There should be 0 or 1 actionifkey.startswith('__action_'):cbname=key[1:]try:callback=getattr(self,cbname)exceptAttributeError:raiseRequestError(self._cw._('invalid action %r'%key))else:returncallback()self._default_publish()self.reset()def_default_publish(self):req=self._cwself.errors=[]self.relations_rql=[]# so we're able to know the main entity from the repository sideif'__maineid'inform:req.set_shared_data('__maineid',form['__maineid'],querydata=True)# no specific action, generic editionself._to_create=req.data['eidmap']={}self._pending_relations=[]todelete=self._cw.get_pending_deletes()toinsert=self._cw.get_pending_inserts()try:methodname=req.form.pop('__method',None)foreidinreq.edited_eids():# __type and eidformparams=req.extract_entity_params(eid,minparams=2)ifmethodnameisnotNone:entity=req.entity_from_eid(eid)method=getattr(entity,methodname)method(formparams)eid=self.edit_entity(formparams)except(RequestError,NothingToEdit):if'__linkto'inreq.formand'eid'inreq.form:self.execute_linkto()elifnot('__delete'inreq.formor'__insert'inreq.formortodeleteortoinsert):raiseValidationError(None,{None:req._('nothing to edit')})forquerydefinself.relations_rql:self._cw.execute(*querydef)# handle relations in newly created entities# XXX find a way to merge _pending_relations and relations_rqlifself._pending_relations:forform,field,entityinself._pending_relations:forquerydefinself.handle_relation(form,field,entity,True):self._cw.execute(*querydef)# XXX this processes *all* pending operations of *all* entitiesifreq.form.has_key('__delete'):todelete+=req.list_form_param('__delete',req.form,pop=True)iftodelete:self.delete_relations(parse_relations_descr(todelete))ifreq.form.has_key('__insert'):toinsert=req.list_form_param('__insert',req.form,pop=True)iftoinsert:self.insert_relations(parse_relations_descr(toinsert))self._cw.remove_pending_operations()def_insert_entity(self,etype,eid,rqlquery):rql=rqlquery.insert_query(etype)try:# get the new entity (in some cases, the type might have# changed as for the File --> Image mutation)entity=self._cw.execute(rql,rqlquery.kwargs).get_entity(0,0)neweid=entity.eidexceptValidationError,ex:self._to_create[eid]=ex.entityifself._cw.json_request:# XXX (syt) why?ex.entity=eidraiseself._to_create[eid]=neweidreturnneweiddef_update_entity(self,eid,rqlquery):rql=rqlquery.update_query(eid)self._cw.execute(rql,rqlquery.kwargs)defedit_entity(self,formparams,multiple=False):"""edit / create / copy an entity and return its eid"""etype=formparams['__type']entity=self._cw.vreg['etypes'].etype_class(etype)(self._cw)entity.eid=formparams['eid']eid=self._get_eid(entity.eid)is_main_entity=self._cw.form.get('__maineid')==formparams['eid']# let a chance to do some entity specific stuff.tnentity.pre_web_edit()# create a rql query from parametersrqlquery=RqlQuery()# process inlined relations at the same time as attributes# this will generate less rql queries and might be useful in# a few dark cornersformid=self._cw.form.get('__form_id','edition')form=self._cw.vreg['forms'].select(formid,self._cw,entity=entity)forfieldinform.fields:ifform.form_field_modified(field):self.handle_formfield(form,field,entity,rqlquery)ifeidisNone:# creation or copyentity.eid=self._insert_entity(etype,formparams['eid'],rqlquery)elifrqlquery.edited:# edition of an existant entityself._update_entity(eid,rqlquery)ifis_main_entity:self.notify_edited(entity)ifformparams.has_key('__delete'):todelete=self._cw.list_form_param('__delete',formparams,pop=True)self.delete_relations(parse_relations_descr(todelete))ifformparams.has_key('__cloned_eid'):entity.copy_relations(typed_eid(formparams['__cloned_eid']))ifformparams.has_key('__insert'):toinsert=self._cw.list_form_param('__insert',formparams,pop=True)self.insert_relations(parse_relations_descr(toinsert))ifis_main_entity:# only execute linkto for the main entityself.execute_linkto(eid)returneiddefhandle_formfield(self,form,field,entity,rqlquery):eschema=entity.e_schematry:forattr,valueinfield.process_posted(form):ifnot((field.role=='subject'andeschema.has_subject_relation(field.name))or(field.role=='object'andeschema.has_object_relation(field.name))):continuerschema=self._cw.schema.rschema(field.name)ifrschema.is_final():rqlquery.kwargs[attr]=valuerqlquery.edited.append('X %s%%(%s)s'%(attr,attr))elifrschema.inlined:self.handle_inlined_relation(form,field,entity,rqlquery)else:self.relations_rql+=self.handle_relation(form,field,entity)exceptProcessFormError,exc:self.errors.append((field,exc))def_action_apply(self):self._default_publish()self.reset()def_action_cancel(self):errorurl=self._cw.form.get('__errorurl')iferrorurl:self._cw.cancel_edition(errorurl)self._cw.message=self._cw._('edit canceled')returnself.reset()def_action_delete(self):self.delete_entities(self._cw.edited_eids(withtype=True))returnself.reset()def_relation_values(self,form,field,entity,late=False):"""handle edition for the (rschema, x) relation of the given entity """values=set()foreidinfield.process_form_value(form):ifnoteid:# AutoCompletionWidgetcontinuetyped_eid=self._get_eid(eid)iftyped_eidisNone:iflate:# eid is still None while it's already a late call# this mean that the associated entity has not been createdraiseException("eid %s is still not created"%eid)self._pending_relations.append((form,field,entity))returnNonevalues.add(typed_eid)returnvaluesdefhandle_inlined_relation(self,form,field,entity,rqlquery):"""handle edition for the (rschema, x) relation of the given entity """origvalues=set(row[0]forrowinentity.related(field.name,field.role))values=self._relation_values(form,field,entity)ifvaluesisNoneorvalues==origvalues:return# not edited / not modified / to do laterattr=field.nameifvalues:rqlquery.kwargs[attr]=iter(values).next()rqlquery.edition.append('X %s%s'%(attr,attr.upper()))rqlquery.restrictions.append('%s eid %%(%s)s'%(attr.upper(),attr))elifentity.has_eid():self.relations_rql+=self.handle_relation(form,field,entity)defhandle_relation(self,form,field,entity,late=False):"""handle edition for the (rschema, x) relation of the given entity """origvalues=set(row[0]forrowinentity.related(field.name,field.role))values=self._relation_values(form,field,entity,late)ifvaluesisNoneorvalues==origvalues:return# not edited / not modified / to do lateretype=entity.e_schemarschema=self._cw.schema.rschema(field.name)iffield.role=='subject':desttype=rschema.objects(etype)[0]card=rschema.rproperty(etype,desttype,'cardinality')[0]subjvar,objvar='X','Y'else:desttype=rschema.subjects(etype)[0]card=rschema.rproperty(desttype,etype,'cardinality')[1]subjvar,objvar='Y','X'eid=entity.eidiffield.role=='object'ornotrschema.inlinedornotvalues:# this is not an inlined relation or no values specified,# explicty remove relationsrql='DELETE %s%s%s WHERE X eid %%(x)s, Y eid %%(y)s'%(subjvar,rschema,objvar)forreidinorigvalues.difference(values):yield(rql,{'x':eid,'y':reid},('x','y'))seteids=values.difference(origvalues)ifseteids:rql='SET %s%s%s WHERE X eid %%(x)s, Y eid %%(y)s'%(subjvar,rschema,objvar)forreidinseteids:yield(rql,{'x':eid,'y':reid},('x','y'))def_get_eid(self,eid):# should be either an int (existant entity) or a variable (to be# created entity)asserteidoreid==0,repr(eid)# 0 is a valid eidtry:returntyped_eid(eid)exceptValueError:try:returnself._to_create[eid]exceptKeyError:self._to_create[eid]=NonereturnNonedef_linked_eids(self,eids,late=False):"""return a list of eids if they are all known, else raise ToDoLater """