--- a/web/views/formrenderers.py Mon Jan 04 18:40:30 2016 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,546 +0,0 @@
-# copyright 2003-2014 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/>.
-"""
-Renderers
----------
-
-.. Note::
- Form renderers are responsible to layout a form to HTML.
-
-Here are the base renderers available:
-
-.. autoclass:: cubicweb.web.views.formrenderers.FormRenderer
-.. autoclass:: cubicweb.web.views.formrenderers.HTableFormRenderer
-.. autoclass:: cubicweb.web.views.formrenderers.EntityCompositeFormRenderer
-.. autoclass:: cubicweb.web.views.formrenderers.EntityFormRenderer
-.. autoclass:: cubicweb.web.views.formrenderers.EntityInlinedFormRenderer
-
-"""
-
-__docformat__ = "restructuredtext en"
-from cubicweb import _
-
-from warnings import warn
-
-from six import text_type
-
-from logilab.mtconverter import xml_escape
-from logilab.common.registry import yes
-
-from cubicweb import tags, uilib
-from cubicweb.appobject import AppObject
-from cubicweb.predicates import is_instance
-from cubicweb.utils import json_dumps, support_args
-from cubicweb.web import eid_param, formwidgets as fwdgs
-
-
-def checkbox(name, value, attrs='', checked=None):
- if checked is None:
- checked = value
- checked = checked and 'checked="checked"' or ''
- return u'<input type="checkbox" name="%s" value="%s" %s %s />' % (
- name, value, checked, attrs)
-
-def field_label(form, field):
- if callable(field.label):
- return field.label(form, field)
- # XXX with 3.6 we can now properly rely on 'if field.role is not None' and
- # stop having a tuple for label
- if isinstance(field.label, tuple): # i.e. needs contextual translation
- return form._cw.pgettext(*field.label)
- return form._cw._(field.label)
-
-
-
-class FormRenderer(AppObject):
- """This is the 'default' renderer, displaying fields in a two columns table:
-
- +--------------+--------------+
- | field1 label | field1 input |
- +--------------+--------------+
- | field2 label | field2 input |
- +--------------+--------------+
-
- +---------+
- | buttons |
- +---------+
- """
- __registry__ = 'formrenderers'
- __regid__ = 'default'
-
- _options = ('display_label', 'display_help',
- 'display_progress_div', 'table_class', 'button_bar_class',
- # add entity since it may be given to select the renderer
- 'entity')
- display_label = True
- display_help = True
- display_progress_div = True
- table_class = u'attributeForm'
- button_bar_class = u'formButtonBar'
-
- def __init__(self, req=None, rset=None, row=None, col=None, **kwargs):
- super(FormRenderer, self).__init__(req, rset=rset, row=row, col=col)
- if self._set_options(kwargs):
- raise ValueError('unconsumed arguments %s' % kwargs)
-
- def _set_options(self, kwargs):
- for key in self._options:
- try:
- setattr(self, key, kwargs.pop(key))
- except KeyError:
- continue
- return kwargs
-
- # renderer interface ######################################################
-
- def render(self, w, form, values):
- self._set_options(values)
- form.add_media()
- data = []
- _w = data.append
- _w(self.open_form(form, values))
- self.render_content(_w, form, values)
- _w(self.close_form(form, values))
- errormsg = self.error_message(form)
- if errormsg:
- data.insert(0, errormsg)
- # NOTE: we call unicode because `tag` objects may be found within data
- # e.g. from the cwtags library
- w(''.join(text_type(x) for x in data))
-
- def render_content(self, w, form, values):
- if self.display_progress_div:
- w(u'<div id="progress">%s</div>' % self._cw._('validating...'))
- w(u'\n<fieldset>\n')
- self.render_fields(w, form, values)
- self.render_buttons(w, form)
- w(u'\n</fieldset>\n')
-
- def render_label(self, form, field):
- if field.label is None:
- return u''
- label = field_label(form, field)
- attrs = {'for': field.dom_id(form)}
- if field.required:
- attrs['class'] = 'required'
- return tags.label(label, **attrs)
-
- def render_help(self, form, field):
- help = []
- descr = field.help
- if callable(descr):
- descr = descr(form, field)
- if descr:
- help.append('<div class="helper">%s</div>' % self._cw._(descr))
- example = field.example_format(self._cw)
- if example:
- help.append('<div class="helper">(%s: %s)</div>'
- % (self._cw._('sample format'), example))
- return u' '.join(help)
-
- # specific methods (mostly to ease overriding) #############################
-
- def error_message(self, form):
- """return formatted error message
-
- This method should be called once inlined field errors has been consumed
- """
- req = self._cw
- errex = form.form_valerror
- # get extra errors
- if errex is not None:
- errormsg = req._('please correct the following errors:')
- errors = form.remaining_errors()
- if errors:
- if len(errors) > 1:
- templstr = u'<li>%s</li>\n'
- else:
- templstr = u' %s\n'
- for field, err in errors:
- if field is None:
- errormsg += templstr % err
- else:
- errormsg += templstr % '%s: %s' % (req._(field), err)
- if len(errors) > 1:
- errormsg = '<ul>%s</ul>' % errormsg
- return u'<div class="errorMessage">%s</div>' % errormsg
- return u''
-
- def open_form(self, form, values, **attrs):
- if form.needs_multipart:
- enctype = u'multipart/form-data'
- else:
- enctype = u'application/x-www-form-urlencoded'
- attrs.setdefault('enctype', enctype)
- attrs.setdefault('method', 'post')
- attrs.setdefault('action', form.form_action() or '#')
- if form.domid:
- attrs.setdefault('id', form.domid)
- if form.onsubmit:
- attrs.setdefault('onsubmit', form.onsubmit)
- if form.cssstyle:
- attrs.setdefault('style', form.cssstyle)
- if form.cssclass:
- attrs.setdefault('class', form.cssclass)
- if form.cwtarget:
- attrs.setdefault('target', form.cwtarget)
- if not form.autocomplete:
- attrs.setdefault('autocomplete', 'off')
- return '<form %s>' % uilib.sgml_attributes(attrs)
-
- def close_form(self, form, values):
- """seems dumb but important for consistency w/ close form, and necessary
- for form renderers overriding open_form to use something else or more than
- and <form>
- """
- out = u'</form>'
- if form.cwtarget:
- attrs = {'name': form.cwtarget, 'id': form.cwtarget,
- 'width': '0px', 'height': '0px',
- 'src': 'javascript: void(0);'}
- out = (u'<iframe %s></iframe>\n' % uilib.sgml_attributes(attrs)) + out
- return out
-
- def render_fields(self, w, form, values):
- fields = self._render_hidden_fields(w, form)
- if fields:
- self._render_fields(fields, w, form)
- self.render_child_forms(w, form, values)
-
- def render_child_forms(self, w, form, values):
- # render
- for childform in getattr(form, 'forms', []):
- self.render_fields(w, childform, values)
-
- def _render_hidden_fields(self, w, form):
- fields = form.fields[:]
- for field in form.fields:
- if not field.is_visible():
- w(field.render(form, self))
- w(u'\n')
- fields.remove(field)
- return fields
-
- def _render_fields(self, fields, w, form):
- byfieldset = {}
- for field in fields:
- byfieldset.setdefault(field.fieldset, []).append(field)
- if form.fieldsets_in_order:
- fieldsets = form.fieldsets_in_order
- else:
- fieldsets = byfieldset
- for fieldset in list(fieldsets):
- try:
- fields = byfieldset.pop(fieldset)
- except KeyError:
- self.warning('no such fieldset: %s (%s)', fieldset, form)
- continue
- w(u'<fieldset>\n')
- if fieldset:
- w(u'<legend>%s</legend>' % self._cw.__(fieldset))
- w(u'<table class="%s">\n' % self.table_class)
- for field in fields:
- w(u'<tr class="%s_%s_row">\n' % (field.name, field.role))
- if self.display_label and field.label is not None:
- w(u'<th class="labelCol">%s</th>\n' % self.render_label(form, field))
- w(u'<td')
- if field.label is None:
- w(u' colspan="2"')
- error = form.field_error(field)
- if error:
- w(u' class="error"')
- w(u'>\n')
- w(field.render(form, self))
- w(u'\n')
- if error:
- self.render_error(w, error)
- if self.display_help:
- w(self.render_help(form, field))
- w(u'</td></tr>\n')
- w(u'</table></fieldset>\n')
- if byfieldset:
- self.warning('unused fieldsets: %s', ', '.join(byfieldset))
-
- def render_buttons(self, w, form):
- if not form.form_buttons:
- return
- w(u'<table class="%s">\n<tr>\n' % self.button_bar_class)
- for button in form.form_buttons:
- w(u'<td>%s</td>\n' % button.render(form))
- w(u'</tr></table>')
-
- def render_error(self, w, err):
- """return validation error for widget's field, if any"""
- w(u'<span class="errorMsg">%s</span>' % err)
-
-
-
-class BaseFormRenderer(FormRenderer):
- """use form_renderer_id = 'base' if you want base FormRenderer layout even
- when selected for an entity
- """
- __regid__ = 'base'
-
-
-
-class HTableFormRenderer(FormRenderer):
- """The 'htable' form renderer display fields horizontally in a table:
-
- +--------------+--------------+---------+
- | field1 label | field2 label | |
- +--------------+--------------+---------+
- | field1 input | field2 input | buttons |
- +--------------+--------------+---------+
- """
- __regid__ = 'htable'
-
- display_help = False
- def _render_fields(self, fields, w, form):
- w(u'<table border="0" class="htableForm">')
- w(u'<tr>')
- for field in fields:
- if self.display_label:
- w(u'<th class="labelCol">%s</th>' % self.render_label(form, field))
- if self.display_help:
- w(self.render_help(form, field))
- # empty slot for buttons
- w(u'<th class="labelCol"> </th>')
- w(u'</tr>')
- w(u'<tr>')
- for field in fields:
- error = form.field_error(field)
- if error:
- w(u'<td class="error">')
- self.render_error(w, error)
- else:
- w(u'<td>')
- w(field.render(form, self))
- w(u'</td>')
- w(u'<td>')
- for button in form.form_buttons:
- w(button.render(form))
- w(u'</td>')
- w(u'</tr>')
- w(u'</table>')
-
- def render_buttons(self, w, form):
- pass
-
-
-class OneRowTableFormRenderer(FormRenderer):
- """The 'htable' form renderer display fields horizontally in a table:
-
- +--------------+--------------+--------------+--------------+---------+
- | field1 label | field1 input | field2 label | field2 input | buttons |
- +--------------+--------------+--------------+--------------+---------+
- """
- __regid__ = 'onerowtable'
-
- display_help = False
- def _render_fields(self, fields, w, form):
- w(u'<table border="0" class="oneRowTableForm">')
- w(u'<tr>')
- for field in fields:
- if self.display_label:
- w(u'<th class="labelCol">%s</th>' % self.render_label(form, field))
- if self.display_help:
- w(self.render_help(form, field))
- error = form.field_error(field)
- if error:
- w(u'<td class="error">')
- self.render_error(w, error)
- else:
- w(u'<td>')
- w(field.render(form, self))
- w(u'</td>')
- w(u'<td>')
- for button in form.form_buttons:
- w(button.render(form))
- w(u'</td>')
- w(u'</tr>')
- w(u'</table>')
-
- def render_buttons(self, w, form):
- pass
-
-
-class EntityCompositeFormRenderer(FormRenderer):
- """This is a specific renderer for the multiple entities edition form
- ('muledit').
-
- Each entity form will be displayed in row off a table, with a check box for
- each entities to indicate which ones are edited. Those checkboxes should be
- automatically updated when something is edited.
- """
- __regid__ = 'composite'
-
- _main_display_fields = None
-
- def render_fields(self, w, form, values):
- if form.parent_form is None:
- w(u'<table class="listing">')
- # get fields from the first subform with something to display (we
- # may have subforms with nothing editable that will simply be
- # skipped later)
- for subform in form.forms:
- subfields = [field for field in subform.fields
- if field.is_visible()]
- if subfields:
- break
- if subfields:
- # main form, display table headers
- w(u'<tr class="header">')
- w(u'<th align="left">%s</th>' %
- tags.input(type='checkbox',
- title=self._cw._('toggle check boxes'),
- onclick="setCheckboxesState('eid', null, this.checked)"))
- for field in subfields:
- w(u'<th>%s</th>' % field_label(form, field))
- w(u'</tr>')
- super(EntityCompositeFormRenderer, self).render_fields(w, form, values)
- if form.parent_form is None:
- w(u'</table>')
- if self._main_display_fields:
- super(EntityCompositeFormRenderer, self)._render_fields(
- self._main_display_fields, w, form)
-
- def _render_fields(self, fields, w, form):
- if form.parent_form is not None:
- entity = form.edited_entity
- values = form.form_previous_values
- qeid = eid_param('eid', entity.eid)
- cbsetstate = "setCheckboxesState('eid', %s, 'checked')" % \
- xml_escape(json_dumps(entity.eid))
- w(u'<tr class="%s">' % (entity.cw_row % 2 and u'even' or u'odd'))
- # XXX turn this into a widget used on the eid field
- w(u'<td>%s</td>' % checkbox('eid', entity.eid,
- checked=qeid in values))
- for field in fields:
- error = form.field_error(field)
- if error:
- w(u'<td class="error">')
- self.render_error(w, error)
- else:
- w(u'<td>')
- if isinstance(field.widget, (fwdgs.Select, fwdgs.CheckBox,
- fwdgs.Radio)):
- field.widget.attrs['onchange'] = cbsetstate
- elif isinstance(field.widget, fwdgs.Input):
- field.widget.attrs['onkeypress'] = cbsetstate
- # XXX else
- w(u'<div>%s</div>' % field.render(form, self))
- w(u'</td>\n')
- w(u'</tr>')
- else:
- self._main_display_fields = fields
-
-
-class EntityFormRenderer(BaseFormRenderer):
- """This is the 'default' renderer for entity's form.
-
- You can still use form_renderer_id = 'base' if you want base FormRenderer
- layout even when selected for an entity.
- """
- __regid__ = 'default'
- # needs some additional points in some case (XXX explain cases)
- __select__ = is_instance('Any') & yes()
-
- _options = FormRenderer._options + ('main_form_title',)
- main_form_title = _('main informations')
-
- def open_form(self, form, values):
- attrs_fs_label = ''
- if self.main_form_title:
- attrs_fs_label += ('<div class="iformTitle"><span>%s</span></div>'
- % self._cw._(self.main_form_title))
- attrs_fs_label += '<div class="formBody">'
- return attrs_fs_label + super(EntityFormRenderer, self).open_form(form, values)
-
- def close_form(self, form, values):
- """seems dumb but important for consistency w/ close form, and necessary
- for form renderers overriding open_form to use something else or more than
- and <form>
- """
- return super(EntityFormRenderer, self).close_form(form, values) + '</div>'
-
- def render_buttons(self, w, form):
- if len(form.form_buttons) == 3:
- w("""<table width="100%%">
- <tbody>
- <tr><td align="center">
- %s
- </td><td style="align: right; width: 50%%;">
- %s
- %s
- </td></tr>
- </tbody>
- </table>""" % tuple(button.render(form) for button in form.form_buttons))
- else:
- super(EntityFormRenderer, self).render_buttons(w, form)
-
-
-class EntityInlinedFormRenderer(EntityFormRenderer):
- """This is a specific renderer for entity's form inlined into another
- entity's form.
- """
- __regid__ = 'inline'
- fieldset_css_class = 'subentity'
-
- def render_title(self, w, form, values):
- w(u'<div class="iformTitle">')
- w(u'<span>%(title)s</span> '
- '#<span class="icounter">%(counter)s</span> ' % values)
- if values['removejs']:
- values['removemsg'] = self._cw._('remove-inlined-entity-form')
- w(u'[<a href="javascript: %(removejs)s;$.noop();">%(removemsg)s</a>]'
- % values)
- w(u'</div>')
-
- def render(self, w, form, values):
- form.add_media()
- self.open_form(w, form, values)
- self.render_title(w, form, values)
- # XXX that stinks
- # cleanup values
- for key in ('title', 'removejs', 'removemsg'):
- values.pop(key, None)
- self.render_fields(w, form, values)
- self.close_form(w, form, values)
-
- def open_form(self, w, form, values):
- try:
- w(u'<div id="div-%(divid)s" onclick="%(divonclick)s">' % values)
- except KeyError:
- w(u'<div id="div-%(divid)s">' % values)
- else:
- w(u'<div id="notice-%s" class="notice">%s</div>' % (
- values['divid'], self._cw._('click on the box to cancel the deletion')))
- w(u'<div class="iformBody">')
-
- def close_form(self, w, form, values):
- w(u'</div></div>')
-
- def render_fields(self, w, form, values):
- w(u'<fieldset id="fs-%(divid)s">' % values)
- fields = self._render_hidden_fields(w, form)
- w(u'</fieldset>')
- w(u'<fieldset class="%s">' % self.fieldset_css_class)
- if fields:
- self._render_fields(fields, w, form)
- self.render_child_forms(w, form, values)
- w(u'</fieldset>')