# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr## This file is part of CubicWeb.## CubicWeb is free software: you can redistribute it and/or modify it under the# terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## CubicWeb is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with CubicWeb. If not, see <http://www.gnu.org/licenses/>."""The edit controller, automatically handling entity form submitting"""__docformat__="restructuredtext en"fromwarningsimportwarnfromrql.utilsimportrqlvar_makerfromlogilab.common.textutilsimportsplitstripfromcubicwebimportBinary,ValidationError,typed_eidfromcubicweb.viewimportEntityAdapter,implements_adapter_compatfromcubicweb.selectorsimportis_instancefromcubicweb.webimport(INTERNAL_FIELD_VALUE,RequestError,NothingToEdit,ProcessFormError)fromcubicweb.web.viewsimportbasecontrollers,autoformclassIEditControlAdapter(EntityAdapter):__regid__='IEditControl'__select__=is_instance('Any')@implements_adapter_compat('IEditControl')defafter_deletion_path(self):"""return (path, parameters) which should be used as redirect information when this entity is being deleted """parent=self.entity.cw_adapt_to('IBreadCrumbs').parent_entity()ifparentisnotNone:returnparent.rest_path(),{}returnstr(self.entity.e_schema).lower(),{}@implements_adapter_compat('IEditControl')defpre_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. """passdefvalerror_eid(eid):try:returntyped_eid(eid)except(ValueError,TypeError):returneidclassRqlQuery(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(basecontrollers.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=[]form=req.form# so we're able to know the main entity from the repository sideif'__maineid'inform:req.set_shared_data('__maineid',form['__maineid'],txdata=True)# no specific action, generic editionself._to_create=req.data['eidmap']={}self._pending_fields=req.data['pendingfields']=set()try:foreidinreq.edited_eids():# __type and eidformparams=req.extract_entity_params(eid,minparams=2)eid=self.edit_entity(formparams)except(RequestError,NothingToEdit),ex:if'__linkto'inreq.formand'eid'inreq.form:self.execute_linkto()elifnot('__delete'inreq.formor'__insert'inreq.form):raiseValidationError(None,{None:unicode(ex)})# handle relations in newly created entitiesifself._pending_fields:forform,fieldinself._pending_fields:self.handle_formfield(form,field)# execute rql to set all relationsforquerydefinself.relations_rql: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:autoform.delete_relations(self._cw,todelete)ifreq.form.has_key('__insert'):warn('[3.6] stop using __insert, support will be removed',DeprecationWarning)toinsert=req.list_form_param('__insert',req.form,pop=True)iftoinsert:autoform.insert_relations(self._cw,toinsert)self._cw.remove_pending_operations()ifself.errors:errors=dict((f.name,unicode(ex))forf,exinself.errors)raiseValidationError(valerror_eid(form.get('__maineid')),errors)def_insert_entity(self,etype,eid,rqlquery):rql=rqlquery.insert_query(etype)try: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):self._cw.execute(rqlquery.update_query(eid),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=valerror_eid(formparams['eid'])is_main_entity=self._cw.form.get('__maineid')==formparams['eid']# let a chance to do some entity specific stuffentity.cw_adapt_to('IEditControl').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)eid=form.actual_eid(entity.eid)form.formvalues={}# init fields value cachetry:editedfields=formparams['_cw_edited_fields']exceptKeyError:raiseRequestError(self._cw._('no edited fields specified for entity %s'%entity.eid))foreditedfieldinsplitstrip(editedfields):try:name,role=editedfield.split('-')except:name=editedfieldrole=Noneifform.field_by_name.im_func.func_code.co_argcount==4:# XXXfield=form.field_by_name(name,role,eschema=entity.e_schema)else:field=form.field_by_name(name,role)iffield.has_been_modified(form):self.handle_formfield(form,field,rqlquery)ifself.errors:errors=dict((f.role_name(),unicode(ex))forf,exinself.errors)raiseValidationError(valerror_eid(entity.eid),errors)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'):# XXX deprecate?todelete=self._cw.list_form_param('__delete',formparams,pop=True)autoform.delete_relations(self._cw,todelete)ifformparams.has_key('__cloned_eid'):entity.copy_relations(typed_eid(formparams['__cloned_eid']))ifis_main_entity:# only execute linkto for the main entityself.execute_linkto(entity.eid)returneiddefhandle_formfield(self,form,field,rqlquery=None):eschema=form.edited_entity.e_schematry:forfield,valueinfield.process_posted(form):ifnot((field.role=='subject'andfield.nameineschema.subjrels)or(field.role=='object'andfield.nameineschema.objrels)):continuerschema=self._cw.vreg.schema.rschema(field.name)ifrschema.final:rqlquery.kwargs[field.name]=valuerqlquery.edited.append('X %s%%(%s)s'%(rschema,rschema))else:ifform.edited_entity.has_eid():origvalues=set(entity.eidforentityinform.edited_entity.related(field.name,field.role,entities=True))else:origvalues=set()ifvalueisNoneorvalue==origvalues:continue# not edited / not modified / to do laterifrschema.inlinedandrqlqueryisnotNoneandfield.role=='subject':self.handle_inlined_relation(form,field,value,origvalues,rqlquery)elifform.edited_entity.has_eid():self.handle_relation(form,field,value,origvalues)else:self._pending_fields.add((form,field))exceptProcessFormError,exc:self.errors.append((field,exc))defhandle_inlined_relation(self,form,field,values,origvalues,rqlquery):"""handle edition for the (rschema, x) relation of the given entity """attr=field.nameifvalues:rqlquery.kwargs[attr]=iter(values).next()rqlquery.edited.append('X %s%s'%(attr,attr.upper()))rqlquery.restrictions.append('%s eid %%(%s)s'%(attr.upper(),attr))elifform.edited_entity.has_eid():self.handle_relation(form,field,values,origvalues)defhandle_relation(self,form,field,values,origvalues):"""handle edition for the (rschema, x) relation of the given entity """etype=form.edited_entity.e_schemarschema=self._cw.vreg.schema.rschema(field.name)iffield.role=='subject':desttype=rschema.objects(etype)[0]card=rschema.rdef(etype,desttype).cardinality[0]subjvar,objvar='X','Y'else:desttype=rschema.subjects(etype)[0]card=rschema.rdef(desttype,etype).cardinality[1]subjvar,objvar='Y','X'eid=form.edited_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):self.relations_rql.append((rql,{'x':eid,'y':reid}))seteids=values.difference(origvalues)ifseteids:rql='SET %s%s%s WHERE X eid %%(x)s, Y eid %%(y)s'%(subjvar,rschema,objvar)forreidinseteids:self.relations_rql.append((rql,{'x':eid,'y':reid}))defdelete_entities(self,eidtypes):"""delete entities from the repository"""redirect_info=set()eidtypes=tuple(eidtypes)foreid,etypeineidtypes:entity=self._cw.entity_from_eid(eid,etype)path,params=entity.cw_adapt_to('IEditControl').after_deletion_path()redirect_info.add((path,tuple(params.iteritems())))entity.cw_delete()iflen(redirect_info)>1:# In the face of ambiguity, refuse the temptation to guess.self._after_deletion_path='view',()else:self._after_deletion_path=iter(redirect_info).next()iflen(eidtypes)>1:self._cw.set_message(self._cw._('entities deleted'))else:self._cw.set_message(self._cw._('entity deleted'))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.set_message(self._cw._('edit canceled'))returnself.reset()def_action_delete(self):self.delete_entities(self._cw.edited_eids(withtype=True))returnself.reset()