diff -r 058bb3dc685f -r 0b59724cb3f2 cubicweb/web/controller.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb/web/controller.py Sat Jan 16 13:48:51 2016 +0100
@@ -0,0 +1,221 @@
+# copyright 2003-2013 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 .
+"""abstract controller classe for CubicWeb web client"""
+
+__docformat__ = "restructuredtext en"
+
+from six import PY2
+
+from logilab.mtconverter import xml_escape
+from logilab.common.registry import yes
+from logilab.common.deprecation import deprecated
+
+from cubicweb.appobject import AppObject
+from cubicweb.mail import format_mail
+from cubicweb.web import LOGGER, Redirect, RequestError
+
+
+NAVIGATION_PARAMETERS = (('vid', '__redirectvid'),
+ ('rql', '__redirectrql'),
+ ('__redirectpath', '__redirectpath'),
+ ('__redirectparams', '__redirectparams'),
+ )
+NAV_FORM_PARAMETERS = tuple(fp for ap, fp in NAVIGATION_PARAMETERS)
+
+def redirect_params(form):
+ """transform redirection parameters into navigation parameters
+ """
+ params = {}
+ # extract navigation parameters from redirection parameters
+ for navparam, redirectparam in NAVIGATION_PARAMETERS:
+ if navparam == redirectparam:
+ continue
+ if redirectparam in form:
+ params[navparam] = form[redirectparam]
+ return params
+
+def append_url_params(url, params):
+ """append raw parameters to the url. Given parameters, if any, are expected
+ to be already url-quoted.
+ """
+ if params:
+ if not '?' in url:
+ url += '?'
+ else:
+ url += '&'
+ url += params
+ return url
+
+
+class Controller(AppObject):
+ """a controller is responsible to make necessary stuff to publish
+ a request. There is usually at least one standard "view" controller
+ and another linked by forms to edit objects ("edit").
+ """
+ __registry__ = 'controllers'
+ __select__ = yes()
+
+ def __init__(self, *args, **kwargs):
+ self.appli = kwargs.pop('appli', None)
+ super(Controller, self).__init__(*args, **kwargs)
+ # attributes use to control after edition redirection
+ self._after_deletion_path = None
+ self._edited_entity = None
+
+ def publish(self, rset=None):
+ """publish the current request, with an optional input rset"""
+ raise NotImplementedError
+
+ # generic methods useful for concrete implementations ######################
+
+ def process_rql(self):
+ """execute rql if specified"""
+ req = self._cw
+ rql = req.form.get('rql')
+ if rql:
+ req.ensure_ro_rql(rql)
+ if PY2 and not isinstance(rql, unicode):
+ rql = unicode(rql, req.encoding)
+ pp = req.vreg['components'].select_or_none('magicsearch', req)
+ if pp is not None:
+ return pp.process_query(rql)
+ if 'eid' in req.form and not isinstance(req.form['eid'], list):
+ return req.eid_rset(req.form['eid'])
+ return None
+
+ def notify_edited(self, entity):
+ """called by edit_entity() to notify which entity is edited"""
+ # NOTE: we can't use entity.rest_path() at this point because
+ # rest_path() could rely on schema constraints (such as a required
+ # relation) that might not be satisfied yet (in case of creations)
+ if not self._edited_entity:
+ self._edited_entity = entity
+
+ @deprecated('[3.18] call view.set_http_cache_headers then '
+ '.is_client_cache_valid() method and return instead')
+ def validate_cache(self, view):
+ view.set_http_cache_headers()
+ self._cw.validate_cache()
+
+ def sendmail(self, recipient, subject, body):
+ senderemail = self._cw.user.cw_adapt_to('IEmailable').get_email()
+ msg = format_mail({'email' : senderemail,
+ 'name' : self._cw.user.dc_title(),},
+ [recipient], body, subject)
+ if not self._cw.vreg.config.sendmails([(msg, [recipient])]):
+ msg = self._cw._('could not connect to the SMTP server')
+ url = self._cw.build_url(__message=msg)
+ raise Redirect(url)
+
+ def reset(self):
+ """reset form parameters and redirect to a view determinated by given
+ parameters
+ """
+ newparams = {}
+ # sets message if needed
+ # XXX - don't call .message twice since it pops the id
+ msg = self._cw.message
+ if msg:
+ newparams['_cwmsgid'] = self._cw.set_redirect_message(msg)
+ if '__action_apply' in self._cw.form:
+ self._return_to_edition_view(newparams)
+ else:
+ self._return_to_original_view(newparams)
+
+ def _return_to_original_view(self, newparams):
+ """validate-button case"""
+ # transforms __redirect[*] parameters into regular form parameters
+ newparams.update(redirect_params(self._cw.form))
+ # find out if we have some explicit `rql` needs
+ rql = newparams.pop('rql', None)
+ # if rql is needed (explicit __redirectrql or multiple deletions for
+ # instance), we have to use the old `view?rql=...` form
+ if rql:
+ path = 'view'
+ newparams['rql'] = rql
+ elif '__redirectpath' in self._cw.form:
+ # if redirect path was explicitly specified in the form, use it
+ path = self._cw.form['__redirectpath']
+ if (self._edited_entity and path != self._edited_entity.rest_path()
+ and '_cwmsgid' in newparams):
+ # are we here on creation or modification?
+ if any(eid == self._edited_entity.eid
+ for eid in self._cw.data.get('eidmap', {}).values()):
+ msg = self._cw._('click here to see created entity')
+ else:
+ msg = self._cw._('click here to see edited entity')
+ msg = u'(%s)' % (xml_escape(self._edited_entity.absolute_url()), msg)
+ self._cw.append_to_redirect_message(msg)
+ elif self._after_deletion_path:
+ # else it should have been set during form processing
+ path, params = self._after_deletion_path
+ params = dict(params) # params given as tuple
+ params.update(newparams)
+ newparams = params
+ elif self._edited_entity:
+ # clear caches in case some attribute participating to the rest path
+ # has been modified
+ self._edited_entity.cw_clear_all_caches()
+ path = self._edited_entity.rest_path()
+ else:
+ path = 'view'
+ url = self._cw.build_url(path, **newparams)
+ url = append_url_params(url, self._cw.form.get('__redirectparams'))
+ raise Redirect(url)
+
+ def _return_to_edition_view(self, newparams):
+ """apply-button case"""
+ form = self._cw.form
+ if self._edited_entity:
+ path = self._edited_entity.rest_path()
+ newparams.pop('rql', None)
+ # else, fallback on the old `view?rql=...` url form
+ elif 'rql' in self._cw.form:
+ path = 'view'
+ newparams['rql'] = form['rql']
+ else:
+ self.warning('the edited data seems inconsistent')
+ path = 'view'
+ # pick up the correction edition view
+ if form.get('__form_id'):
+ newparams['vid'] = form['__form_id']
+ # re-insert copy redirection parameters
+ for redirectparam in NAV_FORM_PARAMETERS:
+ if redirectparam in form:
+ newparams[redirectparam] = form[redirectparam]
+ raise Redirect(self._cw.build_url(path, **newparams))
+
+
+ def _redirect(self, newparams):
+ """Raise a redirect. We use __redirectpath if it specified, else we
+ return to the home page.
+ """
+ if '__redirectpath' in self._cw.form:
+ # if redirect path was explicitly specified in the form, use it
+ path = self._cw.form['__redirectpath']
+ url = self._cw.build_url(path)
+ url = append_url_params(url, self._cw.form.get('__redirectparams'))
+ else:
+ url = self._cw.base_url()
+ # The newparams must update the params in all cases
+ url = self._cw.rebuild_url(url, **newparams)
+ raise Redirect(url)
+
+
+from cubicweb import set_log_methods
+set_log_methods(Controller, LOGGER)