--- a/web/form.py Mon Jan 04 18:40:30 2016 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,284 +0,0 @@
-# copyright 2003-2011 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 <http://www.gnu.org/licenses/>.
-"""abstract form classes for CubicWeb web client"""
-__docformat__ = "restructuredtext en"
-
-from warnings import warn
-
-from six import add_metaclass
-
-from logilab.common.decorators import iclassmethod
-from logilab.common.deprecation import deprecated
-
-from cubicweb.appobject import AppObject
-from cubicweb.view import NOINDEX, NOFOLLOW
-from cubicweb.web import httpcache, formfields, controller, formwidgets as fwdgs
-
-class FormViewMixIn(object):
- """abstract form view mix-in"""
- category = 'form'
- http_cache_manager = httpcache.NoHTTPCacheManager
- add_to_breadcrumbs = False
-
- def html_headers(self):
- """return a list of html headers (eg something to be inserted between
- <head> and </head> of the returned page
-
- by default forms are neither indexed nor followed
- """
- return [NOINDEX, NOFOLLOW]
-
- def linkable(self):
- """override since forms are usually linked by an action,
- so we don't want them to be listed by appli.possible_views
- """
- return False
-
-
-###############################################################################
-
-class metafieldsform(type):
- """metaclass for FieldsForm to retrieve fields defined as class attributes
- and put them into a single ordered list: '_fields_'.
- """
- def __new__(mcs, name, bases, classdict):
- allfields = []
- for base in bases:
- if hasattr(base, '_fields_'):
- allfields += base._fields_
- clsfields = (item for item in classdict.items()
- if isinstance(item[1], formfields.Field))
- for fieldname, field in sorted(clsfields, key=lambda x: x[1].creation_rank):
- if not field.name:
- field.set_name(fieldname)
- allfields.append(field)
- classdict['_fields_'] = allfields
- return super(metafieldsform, mcs).__new__(mcs, name, bases, classdict)
-
-
-class FieldNotFound(Exception):
- """raised by field_by_name when a field with the given name has not been
- found
- """
-
-@add_metaclass(metafieldsform)
-class Form(AppObject):
- __registry__ = 'forms'
-
- parent_form = None
- force_session_key = None
- domid = 'form'
- copy_nav_params = False
- control_fields = set( ('__form_id', '__errorurl', '__domid',
- '__redirectpath', '_cwmsgid',
- ) )
-
- def __init__(self, req, rset=None, row=None, col=None,
- submitmsg=None, mainform=True, **kwargs):
- # process kwargs first so we can properly pass them to Form and match
- # order expectation (ie cw_extra_kwargs populated almost first)
- hiddens, extrakw = self._process_kwargs(kwargs)
- # now call ancestor init
- super(Form, self).__init__(req, rset=rset, row=row, col=col, **extrakw)
- # then continue with further specific initialization
- self.fields = list(self.__class__._fields_)
- for key, val in hiddens:
- self.add_hidden(key, val)
- if mainform:
- formid = kwargs.pop('formvid', self.__regid__)
- self.add_hidden(u'__form_id', formid)
- self._posting = self._cw.form.get('__form_id') == formid
- if mainform:
- self.add_hidden(u'__errorurl', self.session_key())
- self.add_hidden(u'__domid', self.domid)
- self.restore_previous_post(self.session_key())
- # XXX why do we need two different variables (mainform and copy_nav_params ?)
- if self.copy_nav_params:
- for param in controller.NAV_FORM_PARAMETERS:
- if not param in kwargs:
- value = req.form.get(param)
- if value:
- self.add_hidden(param, value)
- if submitmsg is not None:
- self.set_message(submitmsg)
-
- def _process_kwargs(self, kwargs):
- hiddens = []
- extrakw = {}
- # search for navigation parameters and customization of existing
- # attributes; remaining stuff goes in extrakwargs
- for key, val in kwargs.items():
- if key in controller.NAV_FORM_PARAMETERS:
- hiddens.append( (key, val) )
- elif key == 'redirect_path':
- hiddens.append( (u'__redirectpath', val) )
- elif hasattr(self.__class__, key) and not key[0] == '_':
- setattr(self, key, val)
- else:
- extrakw[key] = val
- return hiddens, extrakw
-
- def set_message(self, submitmsg):
- """sets a submitmsg if exists, using _cwmsgid mechanism """
- cwmsgid = self._cw.set_redirect_message(submitmsg)
- self.add_hidden(u'_cwmsgid', cwmsgid)
-
- @property
- def root_form(self):
- """return the root form"""
- if self.parent_form is None:
- return self
- return self.parent_form.root_form
-
- @property
- def form_valerror(self):
- """the validation error exception if any"""
- if self.parent_form is None:
- # unset if restore_previous_post has not be called
- return getattr(self, '_form_valerror', None)
- return self.parent_form.form_valerror
-
- @property
- def form_previous_values(self):
- """previously posted values (on validation error)"""
- if self.parent_form is None:
- # unset if restore_previous_post has not be called
- return getattr(self, '_form_previous_values', {})
- return self.parent_form.form_previous_values
-
- @property
- def posting(self):
- """return True if the form is being posted, False if it is being
- generated.
- """
- # XXX check behaviour on regeneration after error
- if self.parent_form is None:
- return self._posting
- return self.parent_form.posting
-
- @iclassmethod
- def _fieldsattr(cls_or_self):
- if isinstance(cls_or_self, type):
- fields = cls_or_self._fields_
- else:
- fields = cls_or_self.fields
- return fields
-
- @iclassmethod
- def field_by_name(cls_or_self, name, role=None):
- """Return field with the given name and role.
-
- Raise :exc:`FieldNotFound` if the field can't be found.
- """
- for field in cls_or_self._fieldsattr():
- if field.name == name and field.role == role:
- return field
- raise FieldNotFound(name, role)
-
- @iclassmethod
- def fields_by_name(cls_or_self, name, role=None):
- """Return a list of fields with the given name and role."""
- return [field for field in cls_or_self._fieldsattr()
- if field.name == name and field.role == role]
-
- @iclassmethod
- def remove_field(cls_or_self, field):
- """Remove the given field."""
- cls_or_self._fieldsattr().remove(field)
-
- @iclassmethod
- def append_field(cls_or_self, field):
- """Append the given field."""
- cls_or_self._fieldsattr().append(field)
-
- @iclassmethod
- def insert_field_before(cls_or_self, field, name, role=None):
- """Insert the given field before the field of given name and role."""
- bfield = cls_or_self.field_by_name(name, role)
- fields = cls_or_self._fieldsattr()
- fields.insert(fields.index(bfield), field)
-
- @iclassmethod
- def insert_field_after(cls_or_self, field, name, role=None):
- """Insert the given field after the field of given name and role."""
- afield = cls_or_self.field_by_name(name, role)
- fields = cls_or_self._fieldsattr()
- fields.insert(fields.index(afield)+1, field)
-
- @iclassmethod
- def add_hidden(cls_or_self, name, value=None, **kwargs):
- """Append an hidden field to the form. `name`, `value` and extra keyword
- arguments will be given to the field constructor. The inserted field is
- returned.
- """
- kwargs.setdefault('ignore_req_params', True)
- kwargs.setdefault('widget', fwdgs.HiddenInput)
- field = formfields.StringField(name=name, value=value, **kwargs)
- if 'id' in kwargs:
- # by default, hidden input don't set id attribute. If one is
- # explicitly specified, ensure it will be set
- field.widget.setdomid = True
- cls_or_self.append_field(field)
- return field
-
- def session_key(self):
- """return the key that may be used to store / retreive data about a
- previous post which failed because of a validation error
- """
- if self.force_session_key is None:
- return '%s#%s' % (self._cw.url(), self.domid)
- return self.force_session_key
-
- def restore_previous_post(self, sessionkey):
- # get validation session data which may have been previously set.
- # deleting validation errors here breaks form reloading (errors are
- # no more available), they have to be deleted by application's publish
- # method on successful commit
- forminfo = self._cw.session.data.pop(sessionkey, None)
- if forminfo:
- self._form_previous_values = forminfo['values']
- self._form_valerror = forminfo['error']
- # if some validation error occurred on entity creation, we have to
- # get the original variable name from its attributed eid
- foreid = self.form_valerror.entity
- for var, eid in forminfo['eidmap'].items():
- if foreid == eid:
- self.form_valerror.eid = var
- break
- else:
- self.form_valerror.eid = foreid
- else:
- self._form_previous_values = {}
- self._form_valerror = None
-
- def field_error(self, field):
- """return field's error if specified in current validation exception"""
- if self.form_valerror:
- if field.eidparam and self.edited_entity.eid != self.form_valerror.eid:
- return None
- try:
- return self.form_valerror.errors.pop(field.role_name())
- except KeyError:
- if field.role and field.name in self.form_valerror:
- warn('%s: errors key of attribute/relation should be suffixed by "-<role>"'
- % self.form_valerror.__class__, DeprecationWarning)
- return self.form_valerror.errors.pop(field.name)
- return None
-
- def remaining_errors(self):
- return sorted(self.form_valerror.errors.items())