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)