author | Adrien Di Mascio <Adrien.DiMascio@logilab.fr> |
Mon, 10 Nov 2008 19:33:55 +0100 | |
changeset 16 | a70ece4d9d1a |
parent 0 | b97547f5f1fa |
child 431 | 18b4dd650ef8 |
permissions | -rw-r--r-- |
"""abstract controler classe for CubicWeb web client :organization: Logilab :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ __docformat__ = "restructuredtext en" from mx.DateTime import strptime, Error as MxDTError, TimeDelta from cubicweb import typed_eid from cubicweb.common.registerers import priority_registerer from cubicweb.common.selectors import in_group_selector from cubicweb.common.appobject import AppObject from cubicweb.web import LOGGER, Redirect, RequestError NAVIGATION_PARAMETERS = (('vid', '__redirectvid'), ('rql', '__redirectrql'), ('__redirectpath', '__redirectpath'), ('__redirectparams', '__redirectparams'), ) NAV_FORM_PARAMETERS = [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 parse_relations_descr(rdescr): """parse a string describing some relations, in the form subjeids:rtype:objeids where subjeids and objeids are eids separeted by a underscore return an iterator on (subject eid, relation type, object eid) found """ for rstr in rdescr: subjs, rtype, objs = rstr.split(':') for subj in subjs.split('_'): for obj in objs.split('_'): yield typed_eid(subj), rtype, typed_eid(obj) 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' __registerer__ = priority_registerer __selectors__ = (in_group_selector,) require_groups = () def __init__(self, *args, **kwargs): 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 option input rql string (already processed if necessary) """ raise NotImplementedError # generic methods useful for concret implementations ###################### def check_expected_params(self, params): """check that the given list of parameters are specified in the form dictionary """ missing = [] for param in params: if not self.req.form.get(param): missing.append(param) if missing: raise RequestError('missing required parameter(s): %s' % ','.join(missing)) def parse_datetime(self, value, etype='Datetime'): """get a datetime or time from a string (according to etype) Datetime formatted as Date are accepted """ assert etype in ('Datetime', 'Date', 'Time'), etype # XXX raise proper validation error if etype == 'Datetime': format = self.req.property_value('ui.datetime-format') try: return strptime(value, format) except MxDTError: pass elif etype == 'Time': format = self.req.property_value('ui.time-format') try: # (adim) I can't find a way to parse a Time with a custom format date = strptime(value, format) # this returns a DateTime return TimeDelta(date.hour, date.minute, date.second) except MxDTError: raise ValueError('can\'t parse %r (expected %s)' % (value, format)) try: format = self.req.property_value('ui.date-format') return strptime(value, format) except MxDTError: raise ValueError('can\'t parse %r (expected %s)' % (value, format)) 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 delete_entities(self, eidtypes): """delete entities from the repository""" redirect_info = set() eidtypes = tuple(eidtypes) for eid, etype in eidtypes: entity = self.req.eid_rset(eid, etype).get_entity(0, 0) path, params = entity.after_deletion_path() redirect_info.add( (path, tuple(params.iteritems())) ) entity.delete() if len(redirect_info) > 1: # In the face of ambiguity, refuse the temptation to guess. self._after_deletion_path = 'view', () else: self._after_deletion_path = iter(redirect_info).next() if len(eidtypes) > 1: self.req.set_message(self.req._('entities deleted')) else: self.req.set_message(self.req._('entity deleted')) def delete_relations(self, rdefs): """delete relations from the repository""" # FIXME convert to using the syntax subject:relation:eids execute = self.req.execute for subj, rtype, obj in rdefs: rql = 'DELETE X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype execute(rql, {'x': subj, 'y': obj}, ('x', 'y')) self.req.set_message(self.req._('relations deleted')) def insert_relations(self, rdefs): """insert relations into the repository""" execute = self.req.execute for subj, rtype, obj in rdefs: rql = 'SET X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype execute(rql, {'x': subj, 'y': obj}, ('x', 'y')) def reset(self): """reset form parameters and redirect to a view determinated by given parameters """ newparams = {} # sets message if needed if self.req.message: newparams['__message'] = self.req.message if self.req.form.has_key('__action_apply'): self._return_to_edition_view(newparams) if self.req.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.req.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.req.form: # if redirect path was explicitly specified in the form, use it path = self.req.form['__redirectpath'] 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.build_url(path, **newparams) url = append_url_params(url, self.req.form.get('__redirectparams')) raise Redirect(url) def _return_to_edition_view(self, newparams): """apply-button case""" form = self.req.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.req.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.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.req.form: # if redirect path was explicitly specified in the form, use it path = self.req.form['__redirectpath'] url = self.build_url(path, **newparams) url = append_url_params(url, self.req.form.get('__redirectparams')) else: url = self.req.last_visited_page() raise Redirect(url) from cubicweb import set_log_methods set_log_methods(Controller, LOGGER)