cubicweb/web/controller.py
changeset 11057 0b59724cb3f2
parent 10932 cb217b2b3463
child 11767 432f87a63057
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
       
     1 # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """abstract controller classe for CubicWeb web client"""
       
    19 
       
    20 __docformat__ = "restructuredtext en"
       
    21 
       
    22 from six import PY2
       
    23 
       
    24 from logilab.mtconverter import xml_escape
       
    25 from logilab.common.registry import yes
       
    26 from logilab.common.deprecation import deprecated
       
    27 
       
    28 from cubicweb.appobject import AppObject
       
    29 from cubicweb.mail import format_mail
       
    30 from cubicweb.web import LOGGER, Redirect, RequestError
       
    31 
       
    32 
       
    33 NAVIGATION_PARAMETERS = (('vid', '__redirectvid'),
       
    34                          ('rql', '__redirectrql'),
       
    35                          ('__redirectpath', '__redirectpath'),
       
    36                          ('__redirectparams', '__redirectparams'),
       
    37                          )
       
    38 NAV_FORM_PARAMETERS = tuple(fp for ap, fp in NAVIGATION_PARAMETERS)
       
    39 
       
    40 def redirect_params(form):
       
    41     """transform redirection parameters into navigation parameters
       
    42     """
       
    43     params = {}
       
    44     # extract navigation parameters from redirection parameters
       
    45     for navparam, redirectparam in NAVIGATION_PARAMETERS:
       
    46         if navparam == redirectparam:
       
    47             continue
       
    48         if redirectparam in form:
       
    49             params[navparam] = form[redirectparam]
       
    50     return params
       
    51 
       
    52 def append_url_params(url, params):
       
    53     """append raw parameters to the url. Given parameters, if any, are expected
       
    54     to be already url-quoted.
       
    55     """
       
    56     if params:
       
    57         if not '?' in url:
       
    58             url += '?'
       
    59         else:
       
    60             url += '&'
       
    61         url += params
       
    62     return url
       
    63 
       
    64 
       
    65 class Controller(AppObject):
       
    66     """a controller is responsible to make necessary stuff to publish
       
    67     a request. There is usually at least one standard "view" controller
       
    68     and another linked by forms to edit objects ("edit").
       
    69     """
       
    70     __registry__ = 'controllers'
       
    71     __select__ = yes()
       
    72 
       
    73     def __init__(self, *args, **kwargs):
       
    74         self.appli = kwargs.pop('appli', None)
       
    75         super(Controller, self).__init__(*args, **kwargs)
       
    76         # attributes use to control after edition redirection
       
    77         self._after_deletion_path = None
       
    78         self._edited_entity = None
       
    79 
       
    80     def publish(self, rset=None):
       
    81         """publish the current request, with an optional input rset"""
       
    82         raise NotImplementedError
       
    83 
       
    84     # generic methods useful for concrete implementations ######################
       
    85 
       
    86     def process_rql(self):
       
    87         """execute rql if specified"""
       
    88         req = self._cw
       
    89         rql = req.form.get('rql')
       
    90         if rql:
       
    91             req.ensure_ro_rql(rql)
       
    92             if PY2 and not isinstance(rql, unicode):
       
    93                 rql = unicode(rql, req.encoding)
       
    94             pp = req.vreg['components'].select_or_none('magicsearch', req)
       
    95             if pp is not None:
       
    96                 return pp.process_query(rql)
       
    97         if 'eid' in req.form and not isinstance(req.form['eid'], list):
       
    98             return req.eid_rset(req.form['eid'])
       
    99         return None
       
   100 
       
   101     def notify_edited(self, entity):
       
   102         """called by edit_entity() to notify which entity is edited"""
       
   103         # NOTE: we can't use entity.rest_path() at this point because
       
   104         #       rest_path() could rely on schema constraints (such as a required
       
   105         #       relation) that might not be satisfied yet (in case of creations)
       
   106         if not self._edited_entity:
       
   107             self._edited_entity = entity
       
   108 
       
   109     @deprecated('[3.18] call view.set_http_cache_headers then '
       
   110                 '.is_client_cache_valid() method and return instead')
       
   111     def validate_cache(self, view):
       
   112         view.set_http_cache_headers()
       
   113         self._cw.validate_cache()
       
   114 
       
   115     def sendmail(self, recipient, subject, body):
       
   116         senderemail = self._cw.user.cw_adapt_to('IEmailable').get_email()
       
   117         msg = format_mail({'email' : senderemail,
       
   118                            'name' : self._cw.user.dc_title(),},
       
   119                           [recipient], body, subject)
       
   120         if not self._cw.vreg.config.sendmails([(msg, [recipient])]):
       
   121             msg = self._cw._('could not connect to the SMTP server')
       
   122             url = self._cw.build_url(__message=msg)
       
   123             raise Redirect(url)
       
   124 
       
   125     def reset(self):
       
   126         """reset form parameters and redirect to a view determinated by given
       
   127         parameters
       
   128         """
       
   129         newparams = {}
       
   130         # sets message if needed
       
   131         # XXX - don't call .message twice since it pops the id
       
   132         msg = self._cw.message
       
   133         if msg:
       
   134             newparams['_cwmsgid'] = self._cw.set_redirect_message(msg)
       
   135         if '__action_apply' in self._cw.form:
       
   136             self._return_to_edition_view(newparams)
       
   137         else:
       
   138             self._return_to_original_view(newparams)
       
   139 
       
   140     def _return_to_original_view(self, newparams):
       
   141         """validate-button case"""
       
   142         # transforms __redirect[*] parameters into regular form parameters
       
   143         newparams.update(redirect_params(self._cw.form))
       
   144         # find out if we have some explicit `rql` needs
       
   145         rql = newparams.pop('rql', None)
       
   146         # if rql is needed (explicit __redirectrql or multiple deletions for
       
   147         # instance), we have to use the old `view?rql=...` form
       
   148         if rql:
       
   149             path = 'view'
       
   150             newparams['rql'] = rql
       
   151         elif '__redirectpath' in self._cw.form:
       
   152             # if redirect path was explicitly specified in the form, use it
       
   153             path = self._cw.form['__redirectpath']
       
   154             if (self._edited_entity and path != self._edited_entity.rest_path()
       
   155                 and '_cwmsgid' in newparams):
       
   156                 # are we here on creation or modification?
       
   157                 if any(eid == self._edited_entity.eid
       
   158                        for eid in self._cw.data.get('eidmap', {}).values()):
       
   159                     msg = self._cw._('click here to see created entity')
       
   160                 else:
       
   161                     msg = self._cw._('click here to see edited entity')
       
   162                 msg = u'(<a href="%s">%s</a>)' % (xml_escape(self._edited_entity.absolute_url()), msg)
       
   163                 self._cw.append_to_redirect_message(msg)
       
   164         elif self._after_deletion_path:
       
   165             # else it should have been set during form processing
       
   166             path, params = self._after_deletion_path
       
   167             params = dict(params) # params given as tuple
       
   168             params.update(newparams)
       
   169             newparams = params
       
   170         elif self._edited_entity:
       
   171             # clear caches in case some attribute participating to the rest path
       
   172             # has been modified
       
   173             self._edited_entity.cw_clear_all_caches()
       
   174             path = self._edited_entity.rest_path()
       
   175         else:
       
   176             path = 'view'
       
   177         url = self._cw.build_url(path, **newparams)
       
   178         url = append_url_params(url, self._cw.form.get('__redirectparams'))
       
   179         raise Redirect(url)
       
   180 
       
   181     def _return_to_edition_view(self, newparams):
       
   182         """apply-button case"""
       
   183         form = self._cw.form
       
   184         if self._edited_entity:
       
   185             path = self._edited_entity.rest_path()
       
   186             newparams.pop('rql', None)
       
   187         # else, fallback on the old `view?rql=...` url form
       
   188         elif 'rql' in self._cw.form:
       
   189             path = 'view'
       
   190             newparams['rql'] = form['rql']
       
   191         else:
       
   192             self.warning('the edited data seems inconsistent')
       
   193             path = 'view'
       
   194         # pick up the correction edition view
       
   195         if form.get('__form_id'):
       
   196             newparams['vid'] = form['__form_id']
       
   197         # re-insert copy redirection parameters
       
   198         for redirectparam in NAV_FORM_PARAMETERS:
       
   199             if redirectparam in form:
       
   200                 newparams[redirectparam] = form[redirectparam]
       
   201         raise Redirect(self._cw.build_url(path, **newparams))
       
   202 
       
   203 
       
   204     def _redirect(self, newparams):
       
   205         """Raise a redirect. We use __redirectpath if it specified, else we
       
   206         return to the home page.
       
   207         """
       
   208         if '__redirectpath' in self._cw.form:
       
   209             # if redirect path was explicitly specified in the form, use it
       
   210             path = self._cw.form['__redirectpath']
       
   211             url = self._cw.build_url(path)
       
   212             url = append_url_params(url, self._cw.form.get('__redirectparams'))
       
   213         else:
       
   214             url = self._cw.base_url()
       
   215         # The newparams must update the params in all cases
       
   216         url = self._cw.rebuild_url(url, **newparams)
       
   217         raise Redirect(url)
       
   218 
       
   219 
       
   220 from cubicweb import set_log_methods
       
   221 set_log_methods(Controller, LOGGER)