web/views/basecontrollers.py
branchtls-sprint
changeset 1419 7ff24154351d
parent 1398 5fe84a5f7035
child 1467 972517be96dc
equal deleted inserted replaced
1417:06af20e663f2 1419:7ff24154351d
    16 from logilab.common.decorators import cached
    16 from logilab.common.decorators import cached
    17 
    17 
    18 from cubicweb import NoSelectableObject, ValidationError, ObjectNotFound, typed_eid
    18 from cubicweb import NoSelectableObject, ValidationError, ObjectNotFound, typed_eid
    19 from cubicweb.utils import strptime
    19 from cubicweb.utils import strptime
    20 from cubicweb.selectors import yes, match_user_groups
    20 from cubicweb.selectors import yes, match_user_groups
    21 from cubicweb.view import STRICT_DOCTYPE, CW_XHTML_EXTENSIONS
    21 from cubicweb.view import STRICT_DOCTYPE
    22 from cubicweb.common.mail import format_mail
    22 from cubicweb.common.mail import format_mail
    23 from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed
    23 from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed
    24 from cubicweb.web.formrenderers import FormRenderer
    24 from cubicweb.web.formrenderers import FormRenderer
    25 from cubicweb.web.controller import Controller
    25 from cubicweb.web.controller import Controller
    26 from cubicweb.web.views import vid_from_rset
    26 from cubicweb.web.views import vid_from_rset
    28     from cubicweb.web.facet import (FilterRQLBuilder, get_facet,
    28     from cubicweb.web.facet import (FilterRQLBuilder, get_facet,
    29                                     prepare_facets_rqlst)
    29                                     prepare_facets_rqlst)
    30     HAS_SEARCH_RESTRICTION = True
    30     HAS_SEARCH_RESTRICTION = True
    31 except ImportError: # gae
    31 except ImportError: # gae
    32     HAS_SEARCH_RESTRICTION = False
    32     HAS_SEARCH_RESTRICTION = False
    33     
    33 
    34     
    34 
       
    35 def xhtml_wrap(source):
       
    36     head = u'<?xml version="1.0"?>\n' + STRICT_DOCTYPE
       
    37     return head + u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">%s</div>' % source.strip()
       
    38 
       
    39 def jsonize(func):
       
    40     """decorator to sets correct content_type and calls `simplejson.dumps` on
       
    41     results
       
    42     """
       
    43     def wrapper(self, *args, **kwargs):
       
    44         self.req.set_content_type('application/json')
       
    45         result = func(self, *args, **kwargs)
       
    46         return simplejson.dumps(result)
       
    47     return wrapper
       
    48 
       
    49 def xhtmlize(func):
       
    50     """decorator to sets correct content_type and calls `xmlize` on results"""
       
    51     def wrapper(self, *args, **kwargs):
       
    52         self.req.set_content_type(self.req.html_content_type())
       
    53         result = func(self, *args, **kwargs)
       
    54         return xhtml_wrap(result)
       
    55     return wrapper
       
    56 
       
    57 def check_pageid(func):
       
    58     """decorator which checks the given pageid is found in the
       
    59     user's session data
       
    60     """
       
    61     def wrapper(self, *args, **kwargs):
       
    62         data = self.req.get_session_data(self.req.pageid)
       
    63         if data is None:
       
    64             raise RemoteCallFailed(self.req._('pageid-not-found'))
       
    65         return func(self, *args, **kwargs)
       
    66     return wrapper
       
    67 
       
    68 
    35 class LoginController(Controller):
    69 class LoginController(Controller):
    36     id = 'login'
    70     id = 'login'
    37 
    71 
    38     def publish(self, rset=None):
    72     def publish(self, rset=None):
    39         """log in the application"""
    73         """log in the application"""
    42             raise ExplicitLogin()
    76             raise ExplicitLogin()
    43         else:
    77         else:
    44             # Cookie authentication
    78             # Cookie authentication
    45             return self.appli.need_login_content(self.req)
    79             return self.appli.need_login_content(self.req)
    46 
    80 
    47     
    81 
    48 class LogoutController(Controller):
    82 class LogoutController(Controller):
    49     id = 'logout'
    83     id = 'logout'
    50     
    84 
    51     def publish(self, rset=None):
    85     def publish(self, rset=None):
    52         """logout from the application"""
    86         """logout from the application"""
    53         return self.appli.session_handler.logout(self.req)
    87         return self.appli.session_handler.logout(self.req)
    54 
    88 
    55 
    89 
    58     - build result set
    92     - build result set
    59     - select and call main template
    93     - select and call main template
    60     """
    94     """
    61     id = 'view'
    95     id = 'view'
    62     template = 'main-template'
    96     template = 'main-template'
    63     
    97 
    64     def publish(self, rset=None):
    98     def publish(self, rset=None):
    65         """publish a request, returning an encoded string"""
    99         """publish a request, returning an encoded string"""
    66         view, rset = self._select_view_and_rset(rset)
   100         view, rset = self._select_view_and_rset(rset)
    67         self.add_to_breadcrumbs(view)
   101         self.add_to_breadcrumbs(view)
    68         self.validate_cache(view)
   102         self.validate_cache(view)
   129             if target == 'subject':
   163             if target == 'subject':
   130                 rql = 'SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % rtype
   164                 rql = 'SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % rtype
   131             else:
   165             else:
   132                 rql = 'SET Y %s X WHERE X eid %%(x)s, Y eid %%(y)s' % rtype
   166                 rql = 'SET Y %s X WHERE X eid %%(x)s, Y eid %%(y)s' % rtype
   133             for teid in eids:
   167             for teid in eids:
   134                 req.execute(rql, {'x': eid, 'y': typed_eid(teid)}, ('x', 'y')) 
   168                 req.execute(rql, {'x': eid, 'y': typed_eid(teid)}, ('x', 'y'))
   135 
   169 
   136 
   170 
   137 class FormValidatorController(Controller):
   171 class FormValidatorController(Controller):
   138     id = 'validateform'
   172     id = 'validateform'
   139 
   173 
   176         try:
   210         try:
   177             eid = err.entity.eid
   211             eid = err.entity.eid
   178         except AttributeError:
   212         except AttributeError:
   179             eid = err.entity
   213             eid = err.entity
   180         return (False, (eid, err.errors))
   214         return (False, (eid, err.errors))
   181         
   215 
   182 def xmlize(source):
       
   183     head = u'<?xml version="1.0"?>\n' + STRICT_DOCTYPE % CW_XHTML_EXTENSIONS
       
   184     return head + u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">%s</div>' % source.strip()
       
   185 
       
   186 def jsonize(func):
       
   187     """sets correct content_type and calls `simplejson.dumps` on results
       
   188     """
       
   189     def wrapper(self, *args, **kwargs):
       
   190         self.req.set_content_type('application/json')
       
   191         result = func(self, *args, **kwargs)
       
   192         return simplejson.dumps(result)
       
   193     return wrapper
       
   194 
       
   195 
       
   196 def check_pageid(func):
       
   197     """decorator which checks the given pageid is found in the
       
   198     user's session data
       
   199     """
       
   200     def wrapper(self, *args, **kwargs):
       
   201         data = self.req.get_session_data(self.req.pageid)
       
   202         if data is None:
       
   203             raise RemoteCallFailed(self.req._('pageid-not-found'))
       
   204         return func(self, *args, **kwargs)
       
   205     return wrapper
       
   206     
       
   207 
   216 
   208 class JSonController(Controller):
   217 class JSonController(Controller):
   209     id = 'json'
   218     id = 'json'
   210     template = 'main'
   219 
   211 
   220     def publish(self, rset=None):
   212     def publish(self, rset=None):
   221         """call js_* methods. Expected form keys:
   213         mode = self.req.form.get('mode', 'html')
   222 
       
   223         :fname: the method name without the js_ prefix
       
   224         :args: arguments list (json)
       
   225 
       
   226         note: it's the responsability of js_* methods to set the correct
       
   227         response content type
       
   228         """
   214         self.req.pageid = self.req.form.get('pageid')
   229         self.req.pageid = self.req.form.get('pageid')
   215         try:
   230         fname = self.req.form['fname']
   216             func = getattr(self, '%s_exec' % mode)
   231         try:
   217         except AttributeError, ex:
   232             func = getattr(self, 'js_%s' % fname)
   218             self.error('json controller got an unknown mode %r', mode)
   233         except AttributeError:
   219             self.error('\t%s', ex)
   234             raise RemoteCallFailed('no %s method' % fname)
   220             result = u''
   235         # no <arg> attribute means the callback takes no argument
   221         else:
   236         args = self.req.form.get('arg', ())
   222             try:
   237         if not isinstance(args, (list, tuple)):
   223                 result = func(rset)
   238             args = (args,)
   224             except RemoteCallFailed:
   239         args = [simplejson.loads(arg) for arg in args]
   225                 raise
   240         try:
   226             except Exception, ex:
   241             result = func(*args)
   227                 self.exception('an exception occured on json request(rset=%s): %s',
   242         except RemoteCallFailed:
   228                                rset, ex)
   243             raise
   229                 raise RemoteCallFailed(repr(ex))
   244         except Exception, ex:
   230         return result.encode(self.req.encoding)
   245             self.exception('an exception occured while calling js_%s(%s): %s',
       
   246                            fname, args, ex)
       
   247             raise RemoteCallFailed(repr(ex))
       
   248         if result is None:
       
   249             return ''
       
   250         # get unicode on @htmlize methods, encoded string on @jsonize methods
       
   251         elif isinstance(result, unicode):
       
   252             return result.encode(self.req.encoding)
       
   253         return result
       
   254 
       
   255     def _rebuild_posted_form(self, names, values, action=None):
       
   256         form = {}
       
   257         for name, value in zip(names, values):
       
   258             # remove possible __action_xxx inputs
       
   259             if name.startswith('__action'):
       
   260                 continue
       
   261             # form.setdefault(name, []).append(value)
       
   262             if name in form:
       
   263                 curvalue = form[name]
       
   264                 if isinstance(curvalue, list):
       
   265                     curvalue.append(value)
       
   266                 else:
       
   267                     form[name] = [curvalue, value]
       
   268             else:
       
   269                 form[name] = value
       
   270         # simulate click on __action_%s button to help the controller
       
   271         if action:
       
   272             form['__action_%s' % action] = u'whatever'
       
   273         return form
   231 
   274 
   232     def _exec(self, rql, args=None, eidkey=None, rocheck=True):
   275     def _exec(self, rql, args=None, eidkey=None, rocheck=True):
   233         """json mode: execute RQL and return resultset as json"""
   276         """json mode: execute RQL and return resultset as json"""
   234         if rocheck:
   277         if rocheck:
   235             self.ensure_ro_rql(rql)
   278             self.ensure_ro_rql(rql)
   238         except Exception, ex:
   281         except Exception, ex:
   239             self.exception("error in _exec(rql=%s): %s", rql, ex)
   282             self.exception("error in _exec(rql=%s): %s", rql, ex)
   240             return None
   283             return None
   241         return None
   284         return None
   242 
   285 
   243     @jsonize
   286     @xhtmlize
   244     def json_exec(self, rset=None):
   287     def js_view(self):
   245         """json mode: execute RQL and return resultset as json"""
       
   246         rql = self.req.form.get('rql')
       
   247         if rset is None and rql:
       
   248             rset = self._exec(rql)
       
   249         return rset and rset.rows or []
       
   250 
       
   251     def _set_content_type(self, vobj, data):
       
   252         """sets req's content type according to vobj's content type
       
   253         (and xmlize data if needed)
       
   254         """
       
   255         content_type = vobj.content_type
       
   256         if content_type == 'application/xhtml+xml':
       
   257             self.req.set_content_type(content_type)
       
   258             return xmlize(data)
       
   259         return data
       
   260     
       
   261     def html_exec(self, rset=None):
       
   262         # XXX try to use the page-content template
   288         # XXX try to use the page-content template
   263         req = self.req
   289         req = self.req
   264         rql = req.form.get('rql')
   290         rql = req.form.get('rql')
   265         if rset is None and rql:
   291         if rql:
   266             rset = self._exec(rql)
   292             rset = self._exec(rql)
   267         
   293         else:
       
   294             rset = None
   268         vid = req.form.get('vid') or vid_from_rset(req, rset, self.schema)
   295         vid = req.form.get('vid') or vid_from_rset(req, rset, self.schema)
   269         try:
   296         try:
   270             view = self.vreg.select_view(vid, req, rset)
   297             view = self.vreg.select_view(vid, req, rset)
   271         except NoSelectableObject:
   298         except NoSelectableObject:
   272             vid = req.form.get('fallbackvid', 'noresult')
   299             vid = req.form.get('fallbackvid', 'noresult')
   290             stream.write(u'<div class="ajaxHtmlHead">\n') # XXX use a widget ?
   317             stream.write(u'<div class="ajaxHtmlHead">\n') # XXX use a widget ?
   291             stream.write(extresources)
   318             stream.write(extresources)
   292             stream.write(u'</div>\n')
   319             stream.write(u'</div>\n')
   293         if req.form.get('paginate') and divid == 'pageContent':
   320         if req.form.get('paginate') and divid == 'pageContent':
   294             stream.write(u'</div></div>')
   321             stream.write(u'</div></div>')
   295         source = stream.getvalue()
   322         return stream.getvalue()
   296         return self._set_content_type(view, source)
   323 
   297 
   324     @xhtmlize
   298     def rawremote_exec(self, rset=None):
   325     def js_prop_widget(self, propkey, varname, tabindex=None):
   299         """like remote_exec but doesn't change content type"""
   326         """specific method for CWProperty handling"""
   300         # no <arg> attribute means the callback takes no argument
   327         entity = self.vreg.etype_class('CWProperty')(self.req, None, None)
   301         args = self.req.form.get('arg', ())
   328         entity.eid = varname
   302         if not isinstance(args, (list, tuple)):
   329         entity['pkey'] = propkey
   303             args = (args,)
   330         form = self.vreg.select_object('forms', 'edition', self.req, None,
   304         fname = self.req.form['fname']
   331                                        entity=entity)
   305         args = [simplejson.loads(arg) for arg in args]
   332         form.form_build_context()
   306         try:
   333         vfield = form.field_by_name('value')
   307             func = getattr(self, 'js_%s' % fname)
   334         renderer = FormRenderer()
   308         except AttributeError:
   335         return vfield.render(form, renderer, tabindex=tabindex) \
   309             self.exception('rawremote_exec fname=%s', fname)
   336                + renderer.render_help(form, vfield)
   310             return u""
   337 
   311         return func(*args)
   338     @xhtmlize
   312 
   339     def js_component(self, compid, rql, registry='components', extraargs=None):
   313     remote_exec = jsonize(rawremote_exec)
   340         if rql:
   314         
   341             rset = self._exec(rql)
   315     def _rebuild_posted_form(self, names, values, action=None):
   342         else:
   316         form = {}
   343             rset = None
   317         for name, value in zip(names, values):
   344         comp = self.vreg.select_object(registry, compid, self.req, rset)
   318             # remove possible __action_xxx inputs
   345         if extraargs is None:
   319             if name.startswith('__action'):
   346             extraargs = {}
   320                 continue
   347         else: # we receive unicode keys which is not supported by the **syntax
   321             # form.setdefault(name, []).append(value)
   348             extraargs = dict((str(key), value)
   322             if name in form:
   349                              for key, value in extraargs.items())
   323                 curvalue = form[name]
   350         extraargs = extraargs or {}
   324                 if isinstance(curvalue, list):
   351         return comp.dispatch(**extraargs)
   325                     curvalue.append(value)
   352 
   326                 else:
   353     @check_pageid
   327                     form[name] = [curvalue, value]
   354     @xhtmlize
   328             else:
   355     def js_inline_creation_form(self, peid, ttype, rtype, role):
   329                 form[name] = value
   356         view = self.vreg.select_view('inline-creation', self.req, None,
   330         # simulate click on __action_%s button to help the controller
   357                                      etype=ttype, peid=peid, rtype=rtype,
   331         if action:
   358                                      role=role)
   332             form['__action_%s' % action] = u'whatever'
   359         return view.dispatch(etype=ttype, peid=peid, rtype=rtype, role=role)
   333         return form
   360 
   334     
   361     @jsonize
   335     def js_validate_form(self, action, names, values):
   362     def js_validate_form(self, action, names, values):
   336         # XXX this method (and correspoding js calls) should use the new
   363         # XXX this method (and correspoding js calls) should use the new
   337         #     `RemoteCallFailed` mechansim
   364         #     `RemoteCallFailed` mechansim
   338         self.req.form = self._rebuild_posted_form(names, values, action)
   365         self.req.form = self._rebuild_posted_form(names, values, action)
   339         vreg = self.vreg
   366         vreg = self.vreg
   357             self.req.cnx.rollback()
   384             self.req.cnx.rollback()
   358             self.exception('unexpected error in js_validateform')
   385             self.exception('unexpected error in js_validateform')
   359             return (False, self.req._(str(err)))
   386             return (False, self.req._(str(err)))
   360         return (False, '???')
   387         return (False, '???')
   361 
   388 
       
   389     @jsonize
   362     def js_edit_field(self, action, names, values, rtype, eid):
   390     def js_edit_field(self, action, names, values, rtype, eid):
   363         success, args = self.js_validate_form(action, names, values)
   391         success, args = self.js_validate_form(action, names, values)
   364         if success:
   392         if success:
   365             rset = self.req.execute('Any X,N WHERE X eid %%(x)s, X %s N' % rtype,
   393             rset = self.req.execute('Any X,N WHERE X eid %%(x)s, X %s N' % rtype,
   366                                     {'x': eid}, 'x')
   394                                     {'x': eid}, 'x')
   367             entity = rset.get_entity(0, 0)
   395             entity = rset.get_entity(0, 0)
   368             return (success, args, entity.printable_value(rtype))
   396             return (success, args, entity.printable_value(rtype))
   369         else:
   397         else:
   370             return (success, args, None)
   398             return (success, args, None)
   371             
   399 
   372     def js_rql(self, rql):
   400 #     def js_rql(self, rql):
   373         rset = self._exec(rql)
   401 #         rset = self._exec(rql)
   374         return rset and rset.rows or []
   402 #         return rset and rset.rows or []
   375     
   403 
       
   404     @jsonize
   376     def js_i18n(self, msgids):
   405     def js_i18n(self, msgids):
   377         """returns the translation of `msgid`"""
   406         """returns the translation of `msgid`"""
   378         return [self.req._(msgid) for msgid in msgids]
   407         return [self.req._(msgid) for msgid in msgids]
   379 
   408 
       
   409     @jsonize
   380     def js_format_date(self, strdate):
   410     def js_format_date(self, strdate):
   381         """returns the formatted date for `msgid`"""
   411         """returns the formatted date for `msgid`"""
   382         date = strptime(strdate, '%Y-%m-%d %H:%M:%S')
   412         date = strptime(strdate, '%Y-%m-%d %H:%M:%S')
   383         return self.format_date(date)
   413         return self.format_date(date)
   384 
   414 
       
   415     @jsonize
   385     def js_external_resource(self, resource):
   416     def js_external_resource(self, resource):
   386         """returns the URL of the external resource named `resource`"""
   417         """returns the URL of the external resource named `resource`"""
   387         return self.req.external_resource(resource)
   418         return self.req.external_resource(resource)
   388 
   419 
   389     def js_prop_widget(self, propkey, varname, tabindex=None):
       
   390         """specific method for CWProperty handling"""
       
   391         entity = self.vreg.etype_class('CWProperty')(self.req, None, None)
       
   392         entity.eid = varname
       
   393         entity['pkey'] = propkey
       
   394         form = self.vreg.select_object('forms', 'edition', self.req, None,
       
   395                                        entity=entity)
       
   396         form.form_build_context()
       
   397         vfield = form.field_by_name('value')
       
   398         renderer = FormRenderer()
       
   399         return vfield.render(form, renderer, tabindex=tabindex) \
       
   400                    + renderer.render_help(form, vfield)
       
   401 
       
   402     def js_component(self, compid, rql, registry='components', extraargs=None):
       
   403         if rql:
       
   404             rset = self._exec(rql)
       
   405         else:
       
   406             rset = None
       
   407         comp = self.vreg.select_object(registry, compid, self.req, rset)
       
   408         if extraargs is None:
       
   409             extraargs = {}
       
   410         else: # we receive unicode keys which is not supported by the **syntax
       
   411             extraargs = dict((str(key), value)
       
   412                              for key, value in extraargs.items())
       
   413         extraargs = extraargs or {}
       
   414         return self._set_content_type(comp, comp.dispatch(**extraargs))
       
   415 
       
   416     @check_pageid
   420     @check_pageid
       
   421     @jsonize
   417     def js_user_callback(self, cbname):
   422     def js_user_callback(self, cbname):
   418         page_data = self.req.get_session_data(self.req.pageid, {})
   423         page_data = self.req.get_session_data(self.req.pageid, {})
   419         try:
   424         try:
   420             cb = page_data[cbname]
   425             cb = page_data[cbname]
   421         except KeyError:
   426         except KeyError:
   422             return None
   427             return None
   423         return cb(self.req)
   428         return cb(self.req)
   424     
       
   425     def js_unregister_user_callback(self, cbname):
       
   426         self.req.unregister_callback(self.req.pageid, cbname)
       
   427 
       
   428     def js_unload_page_data(self):
       
   429         self.req.del_session_data(self.req.pageid)
       
   430         
       
   431     def js_cancel_edition(self, errorurl):
       
   432         """cancelling edition from javascript
       
   433 
       
   434         We need to clear associated req's data :
       
   435           - errorurl
       
   436           - pending insertions / deletions
       
   437         """
       
   438         self.req.cancel_edition(errorurl)
       
   439     
       
   440     @check_pageid
       
   441     def js_inline_creation_form(self, peid, ttype, rtype, role):
       
   442         view = self.vreg.select_view('inline-creation', self.req, None,
       
   443                                      etype=ttype, peid=peid, rtype=rtype,
       
   444                                      role=role)
       
   445         source = view.dispatch(etype=ttype, peid=peid, rtype=rtype, role=role)
       
   446         return self._set_content_type(view, source)
       
   447 
       
   448     def js_remove_pending_insert(self, (eidfrom, rel, eidto)):
       
   449         self._remove_pending(eidfrom, rel, eidto, 'insert')
       
   450         
       
   451     def js_add_pending_insert(self, (eidfrom, rel, eidto)):
       
   452         self._add_pending(eidfrom, rel, eidto, 'insert')
       
   453         
       
   454     def js_add_pending_inserts(self, tripletlist):
       
   455         for eidfrom, rel, eidto in tripletlist:
       
   456             self._add_pending(eidfrom, rel, eidto, 'insert')
       
   457         
       
   458     def js_remove_pending_delete(self, (eidfrom, rel, eidto)):
       
   459         self._remove_pending(eidfrom, rel, eidto, 'delete')
       
   460     
       
   461     def js_add_pending_delete(self, (eidfrom, rel, eidto)):
       
   462         self._add_pending(eidfrom, rel, eidto, 'delete')
       
   463 
   429 
   464     if HAS_SEARCH_RESTRICTION:
   430     if HAS_SEARCH_RESTRICTION:
       
   431         @jsonize
   465         def js_filter_build_rql(self, names, values):
   432         def js_filter_build_rql(self, names, values):
   466             form = self._rebuild_posted_form(names, values)
   433             form = self._rebuild_posted_form(names, values)
   467             self.req.form = form
   434             self.req.form = form
   468             builder = FilterRQLBuilder(self.req)
   435             builder = FilterRQLBuilder(self.req)
   469             return builder.build_rql()
   436             return builder.build_rql()
   470 
   437 
       
   438         @jsonize
   471         def js_filter_select_content(self, facetids, rql):
   439         def js_filter_select_content(self, facetids, rql):
   472             rqlst = self.vreg.parse(self.req, rql) # XXX Union unsupported yet
   440             rqlst = self.vreg.parse(self.req, rql) # XXX Union unsupported yet
   473             mainvar = prepare_facets_rqlst(rqlst)[0]
   441             mainvar = prepare_facets_rqlst(rqlst)[0]
   474             update_map = {}
   442             update_map = {}
   475             for facetid in facetids:
   443             for facetid in facetids:
   476                 facet = get_facet(self.req, facetid, rqlst.children[0], mainvar)
   444                 facet = get_facet(self.req, facetid, rqlst.children[0], mainvar)
   477                 update_map[facetid] = facet.possible_values()
   445                 update_map[facetid] = facet.possible_values()
   478             return update_map
   446             return update_map
   479 
   447 
       
   448     def js_unregister_user_callback(self, cbname):
       
   449         self.req.unregister_callback(self.req.pageid, cbname)
       
   450 
       
   451     def js_unload_page_data(self):
       
   452         self.req.del_session_data(self.req.pageid)
       
   453 
       
   454     def js_cancel_edition(self, errorurl):
       
   455         """cancelling edition from javascript
       
   456 
       
   457         We need to clear associated req's data :
       
   458           - errorurl
       
   459           - pending insertions / deletions
       
   460         """
       
   461         self.req.cancel_edition(errorurl)
       
   462 
   480     def js_delete_bookmark(self, beid):
   463     def js_delete_bookmark(self, beid):
   481         try:
   464         rql = 'DELETE B bookmarked_by U WHERE B eid %(b)s, U eid %(u)s'
   482             rql = 'DELETE B bookmarked_by U WHERE B eid %(b)s, U eid %(u)s'
   465         self.req.execute(rql, {'b': typed_eid(beid), 'u' : self.req.user.eid})
   483             self.req.execute(rql, {'b': typed_eid(beid), 'u' : self.req.user.eid})
   466 
   484         except Exception, ex:
   467     def js_set_cookie(self, cookiename, cookievalue):
   485             self.exception(unicode(ex))
   468         # XXX we should consider jQuery.Cookie
   486             return self.req._('Problem occured')
   469         cookiename, cookievalue = str(cookiename), str(cookievalue)
       
   470         cookies = self.req.get_cookie()
       
   471         cookies[cookiename] = cookievalue
       
   472         self.req.set_cookie(cookies, cookiename)
       
   473 
       
   474     # relations edition stuff ##################################################
   487 
   475 
   488     def _add_pending(self, eidfrom, rel, eidto, kind):
   476     def _add_pending(self, eidfrom, rel, eidto, kind):
   489         key = 'pending_%s' % kind
   477         key = 'pending_%s' % kind
   490         pendings = self.req.get_session_data(key, set())
   478         pendings = self.req.get_session_data(key, set())
   491         pendings.add( (typed_eid(eidfrom), rel, typed_eid(eidto)) )
   479         pendings.add( (typed_eid(eidfrom), rel, typed_eid(eidto)) )
   492         self.req.set_session_data(key, pendings)
   480         self.req.set_session_data(key, pendings)
   493 
   481 
   494     def _remove_pending(self, eidfrom, rel, eidto, kind):
   482     def _remove_pending(self, eidfrom, rel, eidto, kind):
   495         key = 'pending_%s' % kind        
   483         key = 'pending_%s' % kind
   496         try:
   484         try:
   497             pendings = self.req.get_session_data(key)
   485             pendings = self.req.get_session_data(key)
   498             pendings.remove( (typed_eid(eidfrom), rel, typed_eid(eidto)) )
   486             pendings.remove( (typed_eid(eidfrom), rel, typed_eid(eidto)) )
   499         except:
   487         except:
   500             self.exception('while removing pending eids')
   488             self.exception('while removing pending eids')
   501         else:
   489         else:
   502             self.req.set_session_data(key, pendings)
   490             self.req.set_session_data(key, pendings)
   503 
   491 
       
   492     def js_remove_pending_insert(self, (eidfrom, rel, eidto)):
       
   493         self._remove_pending(eidfrom, rel, eidto, 'insert')
       
   494 
       
   495     def js_add_pending_inserts(self, tripletlist):
       
   496         for eidfrom, rel, eidto in tripletlist:
       
   497             self._add_pending(eidfrom, rel, eidto, 'insert')
       
   498 
       
   499     def js_remove_pending_delete(self, (eidfrom, rel, eidto)):
       
   500         self._remove_pending(eidfrom, rel, eidto, 'delete')
       
   501 
       
   502     def js_add_pending_delete(self, (eidfrom, rel, eidto)):
       
   503         self._add_pending(eidfrom, rel, eidto, 'delete')
       
   504 
       
   505     # XXX specific code. Kill me and my AddComboBox friend
       
   506     @jsonize
   504     def js_add_and_link_new_entity(self, etype_to, rel, eid_to, etype_from, value_from):
   507     def js_add_and_link_new_entity(self, etype_to, rel, eid_to, etype_from, value_from):
   505         # create a new entity
   508         # create a new entity
   506         eid_from = self.req.execute('INSERT %s T : T name "%s"' % ( etype_from, value_from ))[0][0]
   509         eid_from = self.req.execute('INSERT %s T : T name "%s"' % ( etype_from, value_from ))[0][0]
   507         # link the new entity to the main entity
   510         # link the new entity to the main entity
   508         rql = 'SET F %(rel)s T WHERE F eid %(eid_to)s, T eid %(eid_from)s' % {'rel' : rel, 'eid_to' : eid_to, 'eid_from' : eid_from}
   511         rql = 'SET F %(rel)s T WHERE F eid %(eid_to)s, T eid %(eid_from)s' % {'rel' : rel, 'eid_to' : eid_to, 'eid_from' : eid_from}
   509         return eid_from
   512         return eid_from
   510 
   513 
   511     def js_set_cookie(self, cookiename, cookievalue):
       
   512         # XXX we should consider jQuery.Cookie
       
   513         cookiename, cookievalue = str(cookiename), str(cookievalue)
       
   514         cookies = self.req.get_cookie()
       
   515         cookies[cookiename] = cookievalue
       
   516         self.req.set_cookie(cookies, cookiename)
       
   517 
   514 
   518 class SendMailController(Controller):
   515 class SendMailController(Controller):
   519     id = 'sendmail'
   516     id = 'sendmail'
   520     __select__ = match_user_groups('managers', 'users')
   517     __select__ = match_user_groups('managers', 'users')
   521 
   518 
   547         helo_addr = '%s <%s>' % (self.config['sender-name'],
   544         helo_addr = '%s <%s>' % (self.config['sender-name'],
   548                                  self.config['sender-addr'])
   545                                  self.config['sender-addr'])
   549         msg = format_mail({'email' : self.req.user.get_email(),
   546         msg = format_mail({'email' : self.req.user.get_email(),
   550                            'name' : self.req.user.dc_title(),},
   547                            'name' : self.req.user.dc_title(),},
   551                           [recipient], body, subject)
   548                           [recipient], body, subject)
   552         self.smtp.sendmail(helo_addr, [recipient], msg.as_string())    
   549         self.smtp.sendmail(helo_addr, [recipient], msg.as_string())
   553 
   550 
   554     def publish(self, rset=None):
   551     def publish(self, rset=None):
   555         # XXX this allow anybody with access to an cubicweb application to use it as a mail relay
   552         # XXX this allow anybody with access to an cubicweb application to use it as a mail relay
   556         body = self.req.form['mailbody']
   553         body = self.req.form['mailbody']
   557         subject = self.req.form['mailsubject']
   554         subject = self.req.form['mailsubject']
   570     def publish(self, rset=None):
   567     def publish(self, rset=None):
   571         body = self.req.form['description']
   568         body = self.req.form['description']
   572         self.sendmail(self.config['submit-mail'], _('%s error report') % self.config.appid, body)
   569         self.sendmail(self.config['submit-mail'], _('%s error report') % self.config.appid, body)
   573         url = self.build_url(__message=self.req._('bug report sent'))
   570         url = self.build_url(__message=self.req._('bug report sent'))
   574         raise Redirect(url)
   571         raise Redirect(url)
   575     
   572