[notification] don't block while sending mails
Regression from #7c17659c9eae, closes #5190001.
# copyright 2003-2011 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/>."""abstract form classes for CubicWeb web client"""__docformat__="restructuredtext en"fromwarningsimportwarnfromlogilab.common.decoratorsimporticlassmethodfromlogilab.common.deprecationimportdeprecatedfromcubicweb.appobjectimportAppObjectfromcubicweb.viewimportNOINDEX,NOFOLLOWfromcubicweb.webimporthttpcache,formfields,controller,formwidgetsasfwdgsclassFormViewMixIn(object):"""abstract form view mix-in"""category='form'http_cache_manager=httpcache.NoHTTPCacheManageradd_to_breadcrumbs=Falsedefhtml_headers(self):"""return a list of html headers (eg something to be inserted between <head> and </head> of the returned page by default forms are neither indexed nor followed """return[NOINDEX,NOFOLLOW]deflinkable(self):"""override since forms are usually linked by an action, so we don't want them to be listed by appli.possible_views """returnFalse###############################################################################classmetafieldsform(type):"""metaclass for FieldsForm to retrieve fields defined as class attributes and put them into a single ordered list: '_fields_'. """def__new__(mcs,name,bases,classdict):allfields=[]forbaseinbases:ifhasattr(base,'_fields_'):allfields+=base._fields_clsfields=(itemforiteminclassdict.items()ifisinstance(item[1],formfields.Field))forfieldname,fieldinsorted(clsfields,key=lambdax:x[1].creation_rank):ifnotfield.name:field.set_name(fieldname)allfields.append(field)classdict['_fields_']=allfieldsreturnsuper(metafieldsform,mcs).__new__(mcs,name,bases,classdict)classFieldNotFound(Exception):"""raised by field_by_name when a field with the given name has not been found """classForm(AppObject):__metaclass__=metafieldsform__registry__='forms'parent_form=Noneforce_session_key=Nonedomid='form'copy_nav_params=Falsecontrol_fields=set(('__form_id','__errorurl','__domid','__redirectpath','_cwmsgid',))def__init__(self,req,rset=None,row=None,col=None,submitmsg=None,mainform=True,**kwargs):# process kwargs first so we can properly pass them to Form and match# order expectation (ie cw_extra_kwargs populated almost first)hiddens,extrakw=self._process_kwargs(kwargs)# now call ancestor initsuper(Form,self).__init__(req,rset=rset,row=row,col=col,**extrakw)# then continue with further specific initializationself.fields=list(self.__class__._fields_)forkey,valinhiddens:self.add_hidden(key,val)ifmainform:formid=kwargs.pop('formvid',self.__regid__)self.add_hidden(u'__form_id',formid)self._posting=self._cw.form.get('__form_id')==formidifmainform:self.add_hidden(u'__errorurl',self.session_key())self.add_hidden(u'__domid',self.domid)self.restore_previous_post(self.session_key())# XXX why do we need two different variables (mainform and copy_nav_params ?)ifself.copy_nav_params:forparamincontroller.NAV_FORM_PARAMETERS:ifnotparaminkwargs:value=req.form.get(param)ifvalue:self.add_hidden(param,value)ifsubmitmsgisnotNone:self.set_message(submitmsg)def_process_kwargs(self,kwargs):hiddens=[]extrakw={}# search for navigation parameters and customization of existing# attributes; remaining stuff goes in extrakwargsforkey,valinkwargs.iteritems():ifkeyincontroller.NAV_FORM_PARAMETERS:hiddens.append((key,val))elifkey=='redirect_path':hiddens.append((u'__redirectpath',val))elifhasattr(self.__class__,key)andnotkey[0]=='_':setattr(self,key,val)else:extrakw[key]=valreturnhiddens,extrakwdefset_message(self,submitmsg):"""sets a submitmsg if exists, using _cwmsgid mechanism """cwmsgid=self._cw.set_redirect_message(submitmsg)self.add_hidden(u'_cwmsgid',cwmsgid)@propertydefroot_form(self):"""return the root form"""ifself.parent_formisNone:returnselfreturnself.parent_form.root_form@propertydefform_valerror(self):"""the validation error exception if any"""ifself.parent_formisNone:# unset if restore_previous_post has not be calledreturngetattr(self,'_form_valerror',None)returnself.parent_form.form_valerror@propertydefform_previous_values(self):"""previously posted values (on validation error)"""ifself.parent_formisNone:# unset if restore_previous_post has not be calledreturngetattr(self,'_form_previous_values',{})returnself.parent_form.form_previous_values@propertydefposting(self):"""return True if the form is being posted, False if it is being generated. """# XXX check behaviour on regeneration after errorifself.parent_formisNone:returnself._postingreturnself.parent_form.posting@iclassmethoddef_fieldsattr(cls_or_self):ifisinstance(cls_or_self,type):fields=cls_or_self._fields_else:fields=cls_or_self.fieldsreturnfields@iclassmethoddeffield_by_name(cls_or_self,name,role=None):"""Return field with the given name and role. Raise :exc:`FieldNotFound` if the field can't be found. """forfieldincls_or_self._fieldsattr():iffield.name==nameandfield.role==role:returnfieldraiseFieldNotFound(name,role)@iclassmethoddeffields_by_name(cls_or_self,name,role=None):"""Return a list of fields with the given name and role."""return[fieldforfieldincls_or_self._fieldsattr()iffield.name==nameandfield.role==role]@iclassmethoddefremove_field(cls_or_self,field):"""Remove the given field."""cls_or_self._fieldsattr().remove(field)@iclassmethoddefappend_field(cls_or_self,field):"""Append the given field."""cls_or_self._fieldsattr().append(field)@iclassmethoddefinsert_field_before(cls_or_self,field,name,role=None):"""Insert the given field before the field of given name and role."""bfield=cls_or_self.field_by_name(name,role)fields=cls_or_self._fieldsattr()fields.insert(fields.index(bfield),field)@iclassmethoddefinsert_field_after(cls_or_self,field,name,role=None):"""Insert the given field after the field of given name and role."""afield=cls_or_self.field_by_name(name,role)fields=cls_or_self._fieldsattr()fields.insert(fields.index(afield)+1,field)@iclassmethoddefadd_hidden(cls_or_self,name,value=None,**kwargs):"""Append an hidden field to the form. `name`, `value` and extra keyword arguments will be given to the field constructor. The inserted field is returned. """kwargs.setdefault('ignore_req_params',True)kwargs.setdefault('widget',fwdgs.HiddenInput)field=formfields.StringField(name=name,value=value,**kwargs)if'id'inkwargs:# by default, hidden input don't set id attribute. If one is# explicitly specified, ensure it will be setfield.widget.setdomid=Truecls_or_self.append_field(field)returnfielddefsession_key(self):"""return the key that may be used to store / retreive data about a previous post which failed because of a validation error """ifself.force_session_keyisNone:return'%s#%s'%(self._cw.url(),self.domid)returnself.force_session_keydefrestore_previous_post(self,sessionkey):# get validation session data which may have been previously set.# deleting validation errors here breaks form reloading (errors are# no more available), they have to be deleted by application's publish# method on successful commitforminfo=self._cw.session.data.pop(sessionkey,None)ifforminfo:self._form_previous_values=forminfo['values']self._form_valerror=forminfo['error']# if some validation error occurred on entity creation, we have to# get the original variable name from its attributed eidforeid=self.form_valerror.entityforvar,eidinforminfo['eidmap'].items():ifforeid==eid:self.form_valerror.eid=varbreakelse:self.form_valerror.eid=foreidelse:self._form_previous_values={}self._form_valerror=Nonedeffield_error(self,field):"""return field's error if specified in current validation exception"""ifself.form_valerror:iffield.eidparamandself.edited_entity.eid!=self.form_valerror.eid:returnNonetry:returnself.form_valerror.errors.pop(field.role_name())exceptKeyError:iffield.roleandfield.nameinself.form_valerror:warn('%s: errors key of attribute/relation should be suffixed by "-<role>"'%self.form_valerror.__class__,DeprecationWarning)returnself.form_valerror.errors.pop(field.name)returnNonedefremaining_errors(self):returnsorted(self.form_valerror.errors.items())