[web] start a new message system based on id of message stored in session's data
instead of using __message as today, which is problematic (allow message injection).
Also we can have html in messages. Removed the __createdpath hack used to escape those
limitation.
The old system should still work though (and will probably for a while, though
we should progressivly move to the new system where it's possible).
Cleanup request paramaters handling on the way.
"""abstract controller classe for CubicWeb web client
:organization: Logilab
:copyright: 2001-2010 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"
from logilab.mtconverter import xml_escape
from cubicweb.selectors import yes
from cubicweb.appobject import AppObject
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, rql):
"""execute rql if specified"""
# XXX assigning to self really necessary?
self.cw_rset = None
if rql:
self._cw.ensure_ro_rql(rql)
if not isinstance(rql, unicode):
rql = unicode(rql, self._cw.encoding)
pp = self._cw.vreg['components'].select_or_none('magicsearch', self._cw)
if pp is not None:
self.cw_rset = pp.process_query(rql)
return self.cw_rset
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
def validate_cache(self, view):
view.set_http_cache_headers()
self._cw.validate_cache()
def reset(self):
"""reset form parameters and redirect to a view determinated by given
parameters
"""
newparams = {}
# sets message if needed
if self._cw.message:
newparams['_cwmsgid'] = self._cw.set_redirect_message(self._cw.message)
if self._cw.form.has_key('__action_apply'):
self._return_to_edition_view(newparams)
if self._cw.form.has_key('__action_cancel'):
self._return_to_lastpage(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):
# XXX may be here on modification?
msg = u'(<a href="%s">%s</a>)' % (
xml_escape(self._edited_entity.absolute_url()),
self._cw._('click here to see created entity'))
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:
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 _return_to_lastpage(self, newparams):
"""cancel-button case: in this case we are always expecting to go back
where we came from, and this is not easy. Currently we suppose that
__redirectpath is specifying that place if found, else we look in the
request breadcrumbs for the last visited 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, **newparams)
url = append_url_params(url, self._cw.form.get('__redirectparams'))
else:
url = self._cw.last_visited_page()
raise Redirect(url)
from cubicweb import set_log_methods
set_log_methods(Controller, LOGGER)