# HG changeset patch # User Sylvain Thénault # Date 1243534005 -7200 # Node ID ec95eaa2b711b4c960bbc3bb7e3163baabfa6f6c # Parent 56a235af050ee7b6c576ac2f49e461ab490aa770 turn renderers into appobjects diff -r 56a235af050e -r ec95eaa2b711 web/form.py --- a/web/form.py Thu May 28 20:05:54 2009 +0200 +++ b/web/form.py Thu May 28 20:06:45 2009 +0200 @@ -17,18 +17,17 @@ from cubicweb.view import NOINDEX, NOFOLLOW from cubicweb.common import tags from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param, stdmsgs -from cubicweb.web.httpcache import NoHTTPCacheManager +from cubicweb.web import formwidgets as fwdgs, httpcache from cubicweb.web.controller import NAV_FORM_PARAMETERS from cubicweb.web.formfields import (Field, StringField, RelationField, HiddenInitialValueField) -from cubicweb.web import formrenderers -from cubicweb.web import formwidgets as fwdgs + class FormViewMixIn(object): """abstract form view mix-in""" category = 'form' controller = 'edit' - http_cache_manager = NoHTTPCacheManager + http_cache_manager = httpcache.NoHTTPCacheManager add_to_breadcrumbs = False def html_headers(self): @@ -91,7 +90,7 @@ domid = 'entityForm' category = 'form' controller = 'edit' - http_cache_manager = NoHTTPCacheManager + http_cache_manager = httpcache.NoHTTPCacheManager add_to_breadcrumbs = False def html_headers(self): @@ -218,7 +217,6 @@ __registry__ = 'forms' __select__ = yes() - renderer_cls = formrenderers.FormRenderer is_subform = False # attributes overrideable through __init__ @@ -236,6 +234,7 @@ set_error_url = True copy_nav_params = False form_buttons = None # form buttons (button widgets instances) + form_renderer_id = 'default' def __init__(self, req, rset=None, row=None, col=None, submitmsg=None, **kwargs): @@ -334,9 +333,16 @@ """render this form, using the renderer given in args or the default FormRenderer() """ - renderer = values.pop('renderer', self.renderer_cls()) + renderer = values.pop('renderer', None) + if renderer is None: + renderer = self.form_default_renderer() return renderer.render(self, values) + def form_default_renderer(self): + return self.vreg.select_object('formrenderers', self.form_renderer_id, + self.req, self.rset, + row=self.row, col=self.col) + def form_build_context(self, rendervalues=None): """build form context values (the .context attribute which is a dictionary with field instance as key associated to a dictionary @@ -516,6 +522,12 @@ load_bytes) return value + def form_default_renderer(self): + return self.vreg.select_object('formrenderers', self.form_renderer_id, + self.req, self.rset, + row=self.row, col=self.col, + entity=self.edited_entity) + def form_build_context(self, values=None): """overriden to add edit[s|o] hidden fields and to ensure schema fields have eidparam set to True @@ -696,6 +708,7 @@ class CompositeForm(FieldsForm): """form composed for sub-forms""" + form_renderer_id = 'composite' def __init__(self, *args, **kwargs): super(CompositeForm, self).__init__(*args, **kwargs) diff -r 56a235af050e -r ec95eaa2b711 web/formrenderers.py --- a/web/formrenderers.py Thu May 28 20:05:54 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,480 +0,0 @@ -"""form renderers, responsible to layout a form to html - -:organization: Logilab -:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses -""" -__docformat__ = "restructuredtext en" - -from logilab.common import dictattr -from logilab.mtconverter import html_escape - -from simplejson import dumps - -from cubicweb.common import tags -from cubicweb.web import eid_param -from cubicweb.web import formwidgets as fwdgs -from cubicweb.web.widgets import checkbox -from cubicweb.web.formfields import HiddenInitialValueField - - -class FormRenderer(object): - """basic renderer displaying fields in a two columns table label | value - - +--------------+--------------+ - | field1 label | field1 input | - +--------------+--------------+ - | field1 label | field2 input | - +--------------+--------------+ - +---------+ - | buttons | - +---------+ - """ - _options = ('display_fields', 'display_label', 'display_help', - 'display_progress_div', 'table_class', 'button_bar_class') - display_fields = None # None -> all fields - display_label = True - display_help = True - display_progress_div = True - table_class = u'attributeForm' - button_bar_class = u'formButtonBar' - - def __init__(self, **kwargs): - 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, form, values): - self._set_options(values) - form.add_media() - data = [] - w = data.append - w(self.open_form(form, values)) - if self.display_progress_div: - w(u'
%s
' % form.req._('validating...')) - w(u'
') - w(tags.input(type=u'hidden', name=u'__form_id', - value=values.get('formvid', form.id))) - if form.redirect_path: - w(tags.input(type='hidden', name='__redirectpath', value=form.redirect_path)) - self.render_fields(w, form, values) - self.render_buttons(w, form) - w(u'
') - w(u'') - errormsg = self.error_message(form) - if errormsg: - data.insert(0, errormsg) - return '\n'.join(data) - - def render_label(self, form, field): - label = form.req._(field.label) - attrs = {'for': form.context[field]['id']} - if field.required: - attrs['class'] = 'required' - return tags.label(label, **attrs) - - def render_help(self, form, field): - help = [] - descr = field.help - if descr: - help.append('
%s
' % form.req._(descr)) - example = field.example_format(form.req) - if example: - help.append('
(%s: %s)
' - % (form.req._('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 = form.req - errex = form.form_valerror - # get extra errors - if errex is not None: - errormsg = req._('please correct the following errors:') - displayed = form.form_displayed_errors - errors = sorted((field, err) for field, err in errex.errors.items() - if not field in displayed) - if errors: - if len(errors) > 1: - templstr = '
  • %s
  • \n' - else: - templstr = ' %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 = '' % errormsg - return u'
    %s
    ' % errormsg - return u'' - - def open_form(self, form, values): - if form.form_needs_multipart: - enctype = 'multipart/form-data' - else: - enctype = 'application/x-www-form-urlencoded' - if form.action is None: - action = form.req.build_url('edit') - else: - action = form.action - tag = ('
    ' - - def display_field(self, form, field): - if isinstance(field, HiddenInitialValueField): - field = field.visible_field - return (self.display_fields is None - or field.name in form.internal_fields - or (field.name, field.role) in self.display_fields - or (field.name, field.role) in form.internal_fields) - - def render_fields(self, w, form, values): - form.form_build_context(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 self.display_field(form, field): - fields.remove(field) - elif not field.is_visible(): - w(field.render(form, self)) - fields.remove(field) - return fields - - def _render_fields(self, fields, w, form): - w(u'' % self.table_class) - for field in fields: - w(u'') - if self.display_label: - w(u'' % self.render_label(form, field)) - error = form.form_field_error(field) - if error: - w(u'') - w(u'
    %s') - w(error) - else: - w(u'') - w(field.render(form, self)) - if self.display_help: - w(self.render_help(form, field)) - w(u'
    ') - - def render_buttons(self, w, form): - w(u'\n\n' % self.button_bar_class) - for button in form.form_buttons: - w(u'\n' % button.render(form)) - w(u'
    %s
    ') - - -class HTableFormRenderer(FormRenderer): - """display fields horizontally in a table - - +--------------+--------------+---------+ - | field1 label | field2 label | | - +--------------+--------------+---------+ - | field1 input | field2 input | buttons - +--------------+--------------+---------+ - """ - display_help = False - def _render_fields(self, fields, w, form): - w(u'') - w(u'') - for field in fields: - if self.display_label: - w(u'' % self.render_label(form, field)) - if self.display_help: - w(self.render_help(form, field)) - # empty slot for buttons - w(u'') - w(u'') - w(u'') - for field in fields: - error = form.form_field_error(field) - if error: - w(u'') - w(u'') - w(u'') - w(u'
    %s 
    ') - w(error) - else: - w(u'') - w(field.render(form, self)) - w(u'') - for button in form.form_buttons: - w(button.render(form)) - w(u'
    ') - - def render_buttons(self, w, form): - pass - - -class EntityCompositeFormRenderer(FormRenderer): - """specific renderer for multiple entities edition form (muledit)""" - - def render_fields(self, w, form, values): - if not form.is_subform: - w(u'') - super(EntityCompositeFormRenderer, self).render_fields(w, form, values) - if not form.is_subform: - w(u'
    ') - - def _render_fields(self, fields, w, form): - if form.is_subform: - entity = form.edited_entity - values = form.form_previous_values - qeid = eid_param('eid', entity.eid) - cbsetstate = "setCheckboxesState2('eid', %s, 'checked')" % html_escape(dumps(entity.eid)) - w(u'' % (entity.row % 2 and u'even' or u'odd')) - # XXX turn this into a widget used on the eid field - w(u'%s' % checkbox('eid', entity.eid, checked=qeid in values)) - for field in fields: - error = form.form_field_error(field) - if error: - w(u'') - w(error) - else: - w(u'') - 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 - w(u'
    %s
    ' % field.render(form, self)) - w(u'') - else: - # main form, display table headers - w(u'') - w(u'%s' - % tags.input(type='checkbox', title=form.req._('toggle check boxes'), - onclick="setCheckboxesState('eid', this.checked)")) - for field in self.forms[0].fields: - if self.display_field(form, field) and field.is_visible(): - w(u'%s' % form.req._(field.label)) - w(u'') - - - -class EntityFormRenderer(FormRenderer): - """specific renderer for entity edition form (edition)""" - _options = FormRenderer._options + ('display_relations_form',) - display_relations_form = True - - def render(self, form, values): - rendered = super(EntityFormRenderer, self).render(form, values) - return rendered + u'' # close extra div introducted by open_form - - def open_form(self, form, values): - attrs_fs_label = ('
    %s
    ' - % form.req._('main informations')) - attrs_fs_label += '
    ' - return attrs_fs_label + super(EntityFormRenderer, self).open_form(form, values) - - def render_fields(self, w, form, values): - super(EntityFormRenderer, self).render_fields(w, form, values) - self.inline_entities_form(w, form) - if form.edited_entity.has_eid() and self.display_relations_form: - self.relations_form(w, form) - - def _render_fields(self, fields, w, form): - if not form.edited_entity.has_eid() or form.edited_entity.has_perm('update'): - super(EntityFormRenderer, self)._render_fields(fields, w, form) - - def render_buttons(self, w, form): - if len(form.form_buttons) == 3: - w(""" - - - -
    - %s - - %s - %s -
    """ % tuple(button.render(form) for button in form.form_buttons)) - else: - super(EntityFormRenderer, self).render_buttons(w, form) - - def relations_form(self, w, form): - srels_by_cat = form.srelations_by_category('generic', 'add') - if not srels_by_cat: - return u'' - req = form.req - _ = req._ - label = u'%s :' % _('This %s' % form.edited_entity.e_schema).capitalize() - eid = form.edited_entity.eid - w(u'
    ') - w(u'%s' % label) - w(u'') - for rschema, target, related in form.relations_table(): - # already linked entities - if related: - w(u'' % rschema.display_name(req, target)) - w(u'') - w(u'') - pendings = list(form.restore_pending_inserts()) - if not pendings: - w(u'') - else: - for row in pendings: - # soon to be linked to entities - w(u'' % row[1]) - w(u'' % row[3]) - w(u'') - w(u'') - w(u'' % eid) - w(u'') - w(u'' % eid) - w(u'') - w(u'
    %s') - w(u'
      ') - for viewparams in related: - w(u'' - % (viewparams[1], viewparams[0], viewparams[2], viewparams[3])) - if not form.force_display and form.maxrelitems < len(related): - link = (u'' % form.req._('view all')) - w(u'' % link) - w(u'
    ') - w(u'
      
    %s') - w(u'[x]' % - (_('cancel this insert'), row[2])) - w(u'%s' - % (row[1], row[4], html_escape(row[5]))) - w(u'
    ') - w(u'%s' % _('add relation')) - w(u'') - w(u'
    ') - w(u'
    ') - - def inline_entities_form(self, w, form): - """create a form to edit entity's inlined relations""" - if not hasattr(form, 'inlined_relations'): - return - entity = form.edited_entity - __ = form.req.__ - for rschema, targettypes, role in form.inlined_relations(): - # show inline forms only if there's one possible target type - # for rschema - if len(targettypes) != 1: - self.warning('entity related by the %s relation should have ' - 'inlined form but there is multiple target types, ' - 'dunno what to do', rschema) - continue - targettype = targettypes[0].type - if form.should_inline_relation_form(rschema, targettype, role): - w(u'
    ' % rschema) - existant = entity.has_eid() and entity.related(rschema) - if existant: - # display inline-edition view for all existing related entities - w(form.view('inline-edition', existant, rtype=rschema, role=role, - ptype=entity.e_schema, peid=entity.eid)) - if role == 'subject': - card = rschema.rproperty(entity.e_schema, targettype, 'cardinality')[0] - else: - card = rschema.rproperty(targettype, entity.e_schema, 'cardinality')[1] - # there is no related entity and we need at least one: we need to - # display one explicit inline-creation view - if form.should_display_inline_creation_form(rschema, existant, card): - w(form.view('inline-creation', None, etype=targettype, - peid=entity.eid, ptype=entity.e_schema, - rtype=rschema, role=role)) - # we can create more than one related entity, we thus display a link - # to add new related entities - if form.should_display_add_new_relation_link(rschema, existant, card): - divid = "addNew%s%s%s:%s" % (targettype, rschema, role, entity.eid) - w(u'
    ' - % divid) - js = "addInlineCreationForm('%s', '%s', '%s', '%s')" % ( - entity.eid, targettype, rschema, role) - if card in '1?': - js = "toggleVisibility('%s'); %s" % (divid, js) - w(u'+ %s.' - % (rschema, entity.eid, js, __('add a %s' % targettype))) - w(u'
    ') - w(u'
     
    ') - w(u'
    ') - - -class EntityInlinedFormRenderer(EntityFormRenderer): - """specific renderer for entity inlined edition form - (inline-[creation|edition]) - """ - def render(self, form, values): - form.add_media() - data = [] - w = data.append - try: - w(u'
    ' % values) - except KeyError: - w(u'
    ' % values) - else: - w(u'
    %s
    ' % ( - values['divid'], form.req._('click on the box to cancel the deletion'))) - w(u'
    ') - values['removemsg'] = form.req.__('remove this %s' % form.edited_entity.e_schema) - w(u'
    %(title)s ' - '#1 ' - '[%(removemsg)s]
    ' - % values) - # cleanup values - for key in ('title', 'removejs', 'removemsg'): - values.pop(key) - self.render_fields(w, form, values) - w(u'
    ') - return '\n'.join(data) - - def render_fields(self, w, form, values): - form.form_build_context(values) - w(u'
    ' % values) - fields = self._render_hidden_fields(w, form) - w(u'
    ') - w(u'
    ') - if fields: - self._render_fields(fields, w, form) - self.render_child_forms(w, form, values) - self.inline_entities_form(w, form) - w(u'
    ') - diff -r 56a235af050e -r ec95eaa2b711 web/test/unittest_form.py --- a/web/test/unittest_form.py Thu May 28 20:05:54 2009 +0200 +++ b/web/test/unittest_form.py Thu May 28 20:06:45 2009 +0200 @@ -9,12 +9,12 @@ from cubicweb import Binary from cubicweb.devtools.testlib import WebTest from cubicweb.web.form import EntityFieldsForm, FieldsForm -from cubicweb.web.formrenderers import FormRenderer from cubicweb.web.formfields import (IntField, StringField, RichTextField, DateTimeField, DateTimePicker, FileField, EditableFileField) from cubicweb.web.formwidgets import PasswordInput from cubicweb.web.views.workflow import ChangeStateForm +from cubicweb.web.views.formrenderers import FormRenderer class FieldsFormTC(WebTest): @@ -33,7 +33,6 @@ super(EntityFieldsFormTC, self).setUp() self.req = self.request() self.entity = self.user(self.req) - self.renderer = FormRenderer() def test_form_field_vocabulary_unrelated(self): b = self.add_entity('BlogEntry', title=u'di mascii code', content=u'a best-seller') @@ -123,7 +122,8 @@ def _render_entity_field(self, name, form): form.form_build_context({}) - return form.field_by_name(name).render(form, self.renderer) + renderer = FormRenderer(self.req) + return form.field_by_name(name).render(form, renderer) def _test_richtextfield(self, expected): class RTFForm(EntityFieldsForm): diff -r 56a235af050e -r ec95eaa2b711 web/views/basecontrollers.py --- a/web/views/basecontrollers.py Thu May 28 20:05:54 2009 +0200 +++ b/web/views/basecontrollers.py Thu May 28 20:06:45 2009 +0200 @@ -22,9 +22,9 @@ from cubicweb.view import STRICT_DOCTYPE, STRICT_DOCTYPE_NOEXT from cubicweb.common.mail import format_mail from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed, json_dumps -from cubicweb.web.formrenderers import FormRenderer from cubicweb.web.controller import Controller from cubicweb.web.views import vid_from_rset +from cubicweb.web.views.formrenderers import FormRenderer try: from cubicweb.web.facet import (FilterRQLBuilder, get_facet, prepare_facets_rqlst) @@ -340,7 +340,7 @@ entity=entity) form.form_build_context() vfield = form.field_by_name('value') - renderer = FormRenderer() + renderer = FormRenderer(self.req) return vfield.render(form, renderer, tabindex=tabindex) \ + renderer.render_help(form, vfield) diff -r 56a235af050e -r ec95eaa2b711 web/views/cwproperties.py --- a/web/views/cwproperties.py Thu May 28 20:05:54 2009 +0200 +++ b/web/views/cwproperties.py Thu May 28 20:06:45 2009 +0200 @@ -18,10 +18,9 @@ from cubicweb.view import StartupView from cubicweb.web import uicfg, stdmsgs from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn -from cubicweb.web.formrenderers import FormRenderer from cubicweb.web.formfields import FIELDS, StringField from cubicweb.web.formwidgets import Select, Button, SubmitButton -from cubicweb.web.views import primary +from cubicweb.web.views import primary, formrenderers # some string we want to be internationalizable for nicer display of property @@ -201,9 +200,8 @@ form.form_add_hidden('__redirectpath', path) for key in keys: self.form_row(form, key, splitlabel) - renderer = CWPropertiesFormRenderer() - return form.form_render(display_progress_div=False, - renderer=renderer) + renderer = CWPropertiesFormRenderer(self.req, display_progress_div=False) + return form.form_render(renderer=renderer) def form_row(self, form, key, splitlabel): entity = self.entity_for_key(key) @@ -359,8 +357,9 @@ uicfg.autoform_field.tag_attribute(('CWProperty', 'value'), PropertyValueField) -class CWPropertiesFormRenderer(FormRenderer): +class CWPropertiesFormRenderer(formrenderers.FormRenderer): """specific renderer for properties""" + id = 'cwproperties' def open_form(self, form, values): err = '
    ' diff -r 56a235af050e -r ec95eaa2b711 web/views/editforms.py --- a/web/views/editforms.py Thu May 28 20:05:54 2009 +0200 +++ b/web/views/editforms.py Thu May 28 20:06:45 2009 +0200 @@ -24,9 +24,7 @@ from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn from cubicweb.web.formfields import RelationField from cubicweb.web.formwidgets import Button, SubmitButton, ResetButton, Select -from cubicweb.web.formrenderers import (FormRenderer, EntityFormRenderer, - EntityCompositeFormRenderer, - EntityInlinedFormRenderer) +from cubicweb.web.views.formrenderers import FormRenderer def relation_id(eid, rtype, role, reid): @@ -114,11 +112,13 @@ self.w(value) return if rschema.is_final(): - form = self._build_attribute_form(entity, value, rtype, role, reload, row, col, default) + form = self._build_attribute_form(entity, value, rtype, role, + reload, row, col, default) else: - form = self._build_relation_form(entity, value, rtype, role, row, col, vid, default) + form = self._build_relation_form(entity, value, rtype, role, + row, col, vid, default) form.form_add_hidden(u'__maineid', entity.eid) - renderer = FormRenderer(display_label=False, display_help=False, + renderer = FormRenderer(self.req, display_label=False, display_help=False, display_fields=[(rtype, role)], table_class='', button_bar_class='buttonbar', display_progress_div=False) @@ -129,7 +129,7 @@ divid = 'd%s' % make_uid('%s-%s' % (rtype, entity.eid)) event_data = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype, 'vid' : vid, 'default' : default, 'role' : role} - form = EntityFieldsForm(self.req, None, entity=entity, action='#', + form = EntityFieldsForm(self.req, entity=entity, action='#', domid='%s-form' % divid, cssstyle='display: none', onsubmit=("return inlineValidateRelationForm('%(divid)s-form', '%(rtype)s', " @@ -173,7 +173,6 @@ __select__ = one_line_rset() & non_final_entity() & yes() title = _('edition') - renderer = EntityFormRenderer() def cell_call(self, row, col, **kwargs): entity = self.complete_entity(row, col) @@ -186,7 +185,7 @@ row=entity.row, col=entity.col, entity=entity, submitmsg=self.submited_message()) self.init_form(form, entity) - self.w(form.form_render(renderer=self.renderer, formvid=u'edition')) + self.w(form.form_render(formvid=u'edition')) def init_form(self, form, entity): """customize your form before rendering here""" @@ -320,7 +319,7 @@ """ #self.form_title(entity) form = self.vreg.select_object('forms', self.id, self.req, self.rset) - self.w(form.form_render(renderer=EntityCompositeFormRenderer())) + self.w(form.form_render()) class InlineEntityEditionFormView(FormViewMixIn, EntityView): @@ -351,14 +350,15 @@ def render_form(self, entity, peid, rtype, role, **kwargs): """fetch and render the form""" form = self.vreg.select_object('forms', 'edition', self.req, None, - entity=entity, set_error_url=False, + entity=entity, form_renderer_id='inline', + set_error_url=False, copy_nav_params=False) self.add_hiddens(form, entity, peid, rtype, role) divid = '%s-%s-%s' % (peid, rtype, entity.eid) title = self.schema.rschema(rtype).display_name(self.req, role) removejs = self.removejs % (peid, rtype,entity.eid) - self.w(form.form_render(renderer=EntityInlinedFormRenderer(), divid=divid, - title=title, removejs=removejs,**kwargs)) + self.w(form.form_render(divid=divid, title=title, removejs=removejs, + **kwargs)) def add_hiddens(self, form, entity, peid, rtype, role): # to ease overriding (see cubes.vcsfile.views.forms for instance) diff -r 56a235af050e -r ec95eaa2b711 web/views/formrenderers.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/views/formrenderers.py Thu May 28 20:06:45 2009 +0200 @@ -0,0 +1,498 @@ +"""form renderers, responsible to layout a form to html + +:organization: Logilab +:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +""" +__docformat__ = "restructuredtext en" + +from logilab.common import dictattr +from logilab.mtconverter import html_escape + +from simplejson import dumps + +from cubicweb.common import tags +from cubicweb.appobject import AppRsetObject +from cubicweb.selectors import entity_implements, yes +from cubicweb.web import eid_param +from cubicweb.web import formwidgets as fwdgs +from cubicweb.web.widgets import checkbox +from cubicweb.web.formfields import HiddenInitialValueField + + +class FormRenderer(AppRsetObject): + """basic renderer displaying fields in a two columns table label | value + + +--------------+--------------+ + | field1 label | field1 input | + +--------------+--------------+ + | field1 label | field2 input | + +--------------+--------------+ + +---------+ + | buttons | + +---------+ + """ + __registry__ = 'formrenderers' + id = 'default' + + _options = ('display_fields', '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_fields = None # None -> all fields + 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, row, 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, form, values): + self._set_options(values) + form.add_media() + data = [] + w = data.append + w(self.open_form(form, values)) + if self.display_progress_div: + w(u'
    %s
    ' % form.req._('validating...')) + w(u'
    ') + w(tags.input(type=u'hidden', name=u'__form_id', + value=values.get('formvid', form.id))) + if form.redirect_path: + w(tags.input(type='hidden', name='__redirectpath', value=form.redirect_path)) + self.render_fields(w, form, values) + self.render_buttons(w, form) + w(u'
    ') + w(u'') + errormsg = self.error_message(form) + if errormsg: + data.insert(0, errormsg) + return '\n'.join(data) + + def render_label(self, form, field): + label = form.req._(field.label) + attrs = {'for': form.context[field]['id']} + if field.required: + attrs['class'] = 'required' + return tags.label(label, **attrs) + + def render_help(self, form, field): + help = [] + descr = field.help + if descr: + help.append('
    %s
    ' % form.req._(descr)) + example = field.example_format(form.req) + if example: + help.append('
    (%s: %s)
    ' + % (form.req._('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 = form.req + errex = form.form_valerror + # get extra errors + if errex is not None: + errormsg = req._('please correct the following errors:') + displayed = form.form_displayed_errors + errors = sorted((field, err) for field, err in errex.errors.items() + if not field in displayed) + if errors: + if len(errors) > 1: + templstr = '
  • %s
  • \n' + else: + templstr = ' %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 = '
      %s
    ' % errormsg + return u'
    %s
    ' % errormsg + return u'' + + def open_form(self, form, values): + if form.form_needs_multipart: + enctype = 'multipart/form-data' + else: + enctype = 'application/x-www-form-urlencoded' + if form.action is None: + action = form.req.build_url('edit') + else: + action = form.action + tag = ('
    ' + + def display_field(self, form, field): + if isinstance(field, HiddenInitialValueField): + field = field.visible_field + return (self.display_fields is None + or field.name in form.internal_fields + or (field.name, field.role) in self.display_fields + or (field.name, field.role) in form.internal_fields) + + def render_fields(self, w, form, values): + form.form_build_context(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 self.display_field(form, field): + fields.remove(field) + elif not field.is_visible(): + w(field.render(form, self)) + fields.remove(field) + return fields + + def _render_fields(self, fields, w, form): + w(u'' % self.table_class) + for field in fields: + w(u'') + if self.display_label: + w(u'' % self.render_label(form, field)) + error = form.form_field_error(field) + if error: + w(u'') + w(u'
    %s') + w(error) + else: + w(u'') + w(field.render(form, self)) + if self.display_help: + w(self.render_help(form, field)) + w(u'
    ') + + def render_buttons(self, w, form): + w(u'\n\n' % self.button_bar_class) + for button in form.form_buttons: + w(u'\n' % button.render(form)) + w(u'
    %s
    ') + + +class HTableFormRenderer(FormRenderer): + """display fields horizontally in a table + + +--------------+--------------+---------+ + | field1 label | field2 label | | + +--------------+--------------+---------+ + | field1 input | field2 input | buttons + +--------------+--------------+---------+ + """ + id = 'htable' + + display_help = False + def _render_fields(self, fields, w, form): + w(u'') + w(u'') + for field in fields: + if self.display_label: + w(u'' % self.render_label(form, field)) + if self.display_help: + w(self.render_help(form, field)) + # empty slot for buttons + w(u'') + w(u'') + w(u'') + for field in fields: + error = form.form_field_error(field) + if error: + w(u'') + w(u'') + w(u'') + w(u'
    %s 
    ') + w(error) + else: + w(u'') + w(field.render(form, self)) + w(u'') + for button in form.form_buttons: + w(button.render(form)) + w(u'
    ') + + def render_buttons(self, w, form): + pass + + +class EntityCompositeFormRenderer(FormRenderer): + """specific renderer for multiple entities edition form (muledit)""" + id = 'composite' + + def render_fields(self, w, form, values): + if not form.is_subform: + w(u'') + super(EntityCompositeFormRenderer, self).render_fields(w, form, values) + if not form.is_subform: + w(u'
    ') + + def _render_fields(self, fields, w, form): + if form.is_subform: + entity = form.edited_entity + values = form.form_previous_values + qeid = eid_param('eid', entity.eid) + cbsetstate = "setCheckboxesState2('eid', %s, 'checked')" % html_escape(dumps(entity.eid)) + w(u'' % (entity.row % 2 and u'even' or u'odd')) + # XXX turn this into a widget used on the eid field + w(u'%s' % checkbox('eid', entity.eid, checked=qeid in values)) + for field in fields: + error = form.form_field_error(field) + if error: + w(u'') + w(error) + else: + w(u'') + 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 + w(u'
    %s
    ' % field.render(form, self)) + w(u'') + else: + # main form, display table headers + w(u'') + w(u'%s' + % tags.input(type='checkbox', title=form.req._('toggle check boxes'), + onclick="setCheckboxesState('eid', this.checked)")) + for field in self.forms[0].fields: + if self.display_field(form, field) and field.is_visible(): + w(u'%s' % form.req._(field.label)) + w(u'') + +class BaseFormRenderer(FormRenderer): + """use form_renderer_id = 'base' if you don't want adaptation by selection + """ + id = 'base' + +class EntityFormRenderer(FormRenderer): + """specific renderer for entity edition form (edition)""" + __select__ = entity_implements('Any') & yes() + + _options = FormRenderer._options + ('display_relations_form',) + display_relations_form = True + + def render(self, form, values): + rendered = super(EntityFormRenderer, self).render(form, values) + return rendered + u'
    ' # close extra div introducted by open_form + + def open_form(self, form, values): + attrs_fs_label = ('
    %s
    ' + % form.req._('main informations')) + attrs_fs_label += '
    ' + return attrs_fs_label + super(EntityFormRenderer, self).open_form(form, values) + + def render_fields(self, w, form, values): + super(EntityFormRenderer, self).render_fields(w, form, values) + self.inline_entities_form(w, form) + if form.edited_entity.has_eid() and self.display_relations_form: + self.relations_form(w, form) + + def _render_fields(self, fields, w, form): + if not form.edited_entity.has_eid() or form.edited_entity.has_perm('update'): + super(EntityFormRenderer, self)._render_fields(fields, w, form) + + def render_buttons(self, w, form): + if len(form.form_buttons) == 3: + w(""" + + + +
    + %s + + %s + %s +
    """ % tuple(button.render(form) for button in form.form_buttons)) + else: + super(EntityFormRenderer, self).render_buttons(w, form) + + def relations_form(self, w, form): + srels_by_cat = form.srelations_by_category('generic', 'add') + if not srels_by_cat: + return u'' + req = form.req + _ = req._ + label = u'%s :' % _('This %s' % form.edited_entity.e_schema).capitalize() + eid = form.edited_entity.eid + w(u'
    ') + w(u'%s' % label) + w(u'') + for rschema, target, related in form.relations_table(): + # already linked entities + if related: + w(u'' % rschema.display_name(req, target)) + w(u'') + w(u'') + pendings = list(form.restore_pending_inserts()) + if not pendings: + w(u'') + else: + for row in pendings: + # soon to be linked to entities + w(u'' % row[1]) + w(u'' % row[3]) + w(u'') + w(u'') + w(u'' % eid) + w(u'') + w(u'' % eid) + w(u'') + w(u'
    %s') + w(u'
      ') + for viewparams in related: + w(u'' + % (viewparams[1], viewparams[0], viewparams[2], viewparams[3])) + if not form.force_display and form.maxrelitems < len(related): + link = (u'' % form.req._('view all')) + w(u'' % link) + w(u'
    ') + w(u'
      
    %s') + w(u'[x]' % + (_('cancel this insert'), row[2])) + w(u'%s' + % (row[1], row[4], html_escape(row[5]))) + w(u'
    ') + w(u'%s' % _('add relation')) + w(u'') + w(u'
    ') + w(u'
    ') + + def inline_entities_form(self, w, form): + """create a form to edit entity's inlined relations""" + if not hasattr(form, 'inlined_relations'): + return + entity = form.edited_entity + __ = form.req.__ + for rschema, targettypes, role in form.inlined_relations(): + # show inline forms only if there's one possible target type + # for rschema + if len(targettypes) != 1: + self.warning('entity related by the %s relation should have ' + 'inlined form but there is multiple target types, ' + 'dunno what to do', rschema) + continue + targettype = targettypes[0].type + if form.should_inline_relation_form(rschema, targettype, role): + w(u'
    ' % rschema) + existant = entity.has_eid() and entity.related(rschema) + if existant: + # display inline-edition view for all existing related entities + w(form.view('inline-edition', existant, rtype=rschema, role=role, + ptype=entity.e_schema, peid=entity.eid)) + if role == 'subject': + card = rschema.rproperty(entity.e_schema, targettype, 'cardinality')[0] + else: + card = rschema.rproperty(targettype, entity.e_schema, 'cardinality')[1] + # there is no related entity and we need at least one: we need to + # display one explicit inline-creation view + if form.should_display_inline_creation_form(rschema, existant, card): + w(form.view('inline-creation', None, etype=targettype, + peid=entity.eid, ptype=entity.e_schema, + rtype=rschema, role=role)) + # we can create more than one related entity, we thus display a link + # to add new related entities + if form.should_display_add_new_relation_link(rschema, existant, card): + divid = "addNew%s%s%s:%s" % (targettype, rschema, role, entity.eid) + w(u'
    ' + % divid) + js = "addInlineCreationForm('%s', '%s', '%s', '%s')" % ( + entity.eid, targettype, rschema, role) + if card in '1?': + js = "toggleVisibility('%s'); %s" % (divid, js) + w(u'+ %s.' + % (rschema, entity.eid, js, __('add a %s' % targettype))) + w(u'
    ') + w(u'
     
    ') + w(u'
    ') + + +class EntityInlinedFormRenderer(EntityFormRenderer): + """specific renderer for entity inlined edition form + (inline-[creation|edition]) + """ + id = 'inline' + + def render(self, form, values): + form.add_media() + data = [] + w = data.append + try: + w(u'
    ' % values) + except KeyError: + w(u'
    ' % values) + else: + w(u'
    %s
    ' % ( + values['divid'], form.req._('click on the box to cancel the deletion'))) + w(u'
    ') + values['removemsg'] = form.req.__('remove this %s' % form.edited_entity.e_schema) + w(u'
    %(title)s ' + '#1 ' + '[%(removemsg)s]
    ' + % values) + # cleanup values + for key in ('title', 'removejs', 'removemsg'): + values.pop(key) + self.render_fields(w, form, values) + w(u'
    ') + return '\n'.join(data) + + def render_fields(self, w, form, values): + form.form_build_context(values) + w(u'
    ' % values) + fields = self._render_hidden_fields(w, form) + w(u'
    ') + w(u'
    ') + if fields: + self._render_fields(fields, w, form) + self.render_child_forms(w, form, values) + self.inline_entities_form(w, form) + w(u'
    ') + diff -r 56a235af050e -r ec95eaa2b711 web/views/management.py --- a/web/views/management.py Thu May 28 20:05:54 2009 +0200 +++ b/web/views/management.py Thu May 28 20:06:45 2009 +0200 @@ -17,7 +17,7 @@ from cubicweb.web import formwidgets from cubicweb.web.form import FieldsForm, EntityFieldsForm from cubicweb.web.formfields import guess_field -from cubicweb.web.formrenderers import HTableFormRenderer +from cubicweb.web.views.formrenderers import HTableFormRenderer SUBMIT_MSGID = _('Submit bug report') MAIL_SUBMIT_MSGID = _('Submit bug report by mail') @@ -183,7 +183,8 @@ form.append_field(field) field = guess_field(cwpermschema, self.schema.rschema('require_group')) form.append_field(field) - self.w(form.form_render(renderer=HTableFormRenderer(display_progress_div=False))) + renderer = HTableFormRenderer(self.req, display_progress_div=False) + self.w(form.form_render(renderer=renderer)) class ErrorView(AnyRsetView): diff -r 56a235af050e -r ec95eaa2b711 web/views/massmailing.py --- a/web/views/massmailing.py Thu May 28 20:05:54 2009 +0200 +++ b/web/views/massmailing.py Thu May 28 20:06:45 2009 +0200 @@ -6,6 +6,7 @@ :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ __docformat__ = "restructuredtext en" +_ = unicode import operator @@ -15,11 +16,10 @@ from cubicweb.web import stdmsgs from cubicweb.web.action import Action from cubicweb.web.form import FieldsForm, FormViewMixIn -from cubicweb.web.formrenderers import FormRenderer from cubicweb.web.formfields import StringField from cubicweb.web.formwidgets import CheckBox, TextInput, AjaxWidget, ImgButton +from cubicweb.web.views import formrenderers -_ = unicode class SendEmailAction(Action): id = 'sendemail' @@ -45,10 +45,12 @@ subject = StringField(label=_('Subject:')) mailbody = StringField(widget=AjaxWidget(wdgtype='TemplateTextField', inputid='mailbody')) + form_buttons = [ImgButton('sendbutton', "javascript: $('#sendmail').submit()", _('send email'), 'SEND_EMAIL_ICON'), ImgButton('cancelbutton', "javascript: history.back()", stdmsgs.BUTTON_CANCEL, 'CANCEL_EMAIL_ICON')] + form_renderer_id = id def form_field_vocabulary(self, field): if field.name == 'recipient': @@ -79,7 +81,8 @@ helpmsg, u'\n'.join(substs)) -class MassMailingFormRenderer(FormRenderer): +class MassMailingFormRenderer(formrenderers.FormRenderer): + id = 'massmailing' button_bar_class = u'toolbar' def _render_fields(self, fields, w, form): @@ -125,4 +128,4 @@ from_addr = '%s <%s>' % (req.user.dc_title(), req.user.get_email()) form = self.vreg.select_object('forms', 'massmailing', self.req, self.rset, action='sendmail', domid='sendmail') - self.w(form.form_render(sender=from_addr, renderer=MassMailingFormRenderer())) + self.w(form.form_render(sender=from_addr)) diff -r 56a235af050e -r ec95eaa2b711 web/views/workflow.py --- a/web/views/workflow.py Thu May 28 20:05:54 2009 +0200 +++ b/web/views/workflow.py Thu May 28 20:06:45 2009 +0200 @@ -31,12 +31,14 @@ class ChangeStateForm(form.EntityFieldsForm): id = 'changestate' + form_renderer_id = 'base' # don't want EntityFormRenderer + form_buttons = [SubmitButton(stdmsgs.YES), + Button(stdmsgs.NO, cwaction='cancel')] + __method = StringField(name='__method', initial='set_state', widget=HiddenInput) state = StringField(eidparam=True, widget=HiddenInput) trcomment = RichTextField(label=_('comment:'), eidparam=True) - form_buttons = [SubmitButton(stdmsgs.YES), - Button(stdmsgs.NO, cwaction='cancel')] class ChangeStateFormView(FormViewMixIn, view.EntityView):