--- a/appobject.py Thu May 28 19:07:41 2009 +0200
+++ b/appobject.py Thu May 28 20:07:18 2009 +0200
@@ -188,14 +188,8 @@
return rql
def view(self, __vid, rset=None, __fallback_vid=None, **kwargs):
- """shortcut to self.vreg.render method avoiding to pass self.req"""
- try:
- view = self.vreg.select_view(__vid, self.req, rset, **kwargs)
- except NoSelectableObject:
- if __fallback_vid is None:
- raise
- view = self.vreg.select_view(__fallback_vid, self.req, rset, **kwargs)
- return view.render(**kwargs)
+ """shortcut to self.vreg.view method avoiding to pass self.req"""
+ return self.vreg.view(__vid, self.req, rset, __fallback_vid, **kwargs)
def initialize_varmaker(self):
varmaker = self.req.get_page_data('rql_varmaker')
@@ -301,6 +295,35 @@
return self.req.property_value('ui.float-format') % num
return u''
+ 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 todatetime(strptime(value, format))
+ except:
+ 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 datetime.time(date.hour, date.minute, date.second)
+ except:
+ raise ValueError('can\'t parse %r (expected %s)' % (value, format))
+ try:
+ format = self.req.property_value('ui.date-format')
+ dt = strptime(value, format)
+ if etype == 'Datetime':
+ return todatetime(dt)
+ return todate(dt)
+ except:
+ raise ValueError('can\'t parse %r (expected %s)' % (value, format))
+
# security related methods ################################################
def ensure_ro_rql(self, rql):
--- a/cwvreg.py Thu May 28 19:07:41 2009 +0200
+++ b/cwvreg.py Thu May 28 20:07:18 2009 +0200
@@ -249,6 +249,16 @@
self.exception('error while trying to list possible %s views for %s',
vid, rset)
+ def view(self, __vid, req, rset=None, __fallback_vid=None, **kwargs):
+ """shortcut to self.vreg.render method avoiding to pass self.req"""
+ try:
+ view = self.select_view(__vid, req, rset, **kwargs)
+ except NoSelectableObject:
+ if __fallback_vid is None:
+ raise
+ view = self.select_view(__fallback_vid, req, rset, **kwargs)
+ return view.render(**kwargs)
+
def select_box(self, oid, *args, **kwargs):
"""return the most specific view according to the result set"""
try:
@@ -270,12 +280,11 @@
except (NoSelectableObject, ObjectNotFound):
return
- def select_view(self, __vid, req, rset, **kwargs):
+ def select_view(self, __vid, req, rset=None, **kwargs):
"""return the most specific view according to the result set"""
views = self.registry_objects('views', __vid)
return self.select(views, req, rset, **kwargs)
-
# properties handling #####################################################
def user_property_keys(self, withsitewide=False):
--- a/selectors.py Thu May 28 19:07:41 2009 +0200
+++ b/selectors.py Thu May 28 20:07:18 2009 +0200
@@ -40,7 +40,6 @@
: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"
import logging
@@ -246,6 +245,7 @@
if kwargs.get('entity'):
score = self.score_entity(kwargs['entity'])
elif row is None:
+ col = col or 0
for row, rowvalue in enumerate(rset.rows):
if rowvalue[col] is None: # outer join
continue
@@ -256,6 +256,7 @@
return escore
score += escore
else:
+ col = col or 0
etype = rset.description[row][col]
if etype is not None: # outer join
score = self.score(req, rset, row, col)
--- a/view.py Thu May 28 19:07:41 2009 +0200
+++ b/view.py Thu May 28 20:07:18 2009 +0200
@@ -207,7 +207,7 @@
# view utilities ##########################################################
- def wview(self, __vid, rset, __fallback_vid=None, **kwargs):
+ def wview(self, __vid, rset=None, __fallback_vid=None, **kwargs):
"""shortcut to self.view method automatically passing self.w as argument
"""
self.view(__vid, rset, __fallback_vid, w=self.w, **kwargs)
--- a/web/controller.py Thu May 28 19:07:41 2009 +0200
+++ b/web/controller.py Thu May 28 20:07:18 2009 +0200
@@ -108,35 +108,6 @@
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 todatetime(strptime(value, format))
- except:
- 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 datetime.time(date.hour, date.minute, date.second)
- except:
- raise ValueError('can\'t parse %r (expected %s)' % (value, format))
- try:
- format = self.req.property_value('ui.date-format')
- dt = strptime(value, format)
- if etype == 'Datetime':
- return todatetime(dt)
- return todate(dt)
- except:
- 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"""
--- a/web/form.py Thu May 28 19:07:41 2009 +0200
+++ b/web/form.py Thu May 28 20:07:18 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)
--- a/web/formfields.py Thu May 28 19:07:41 2009 +0200
+++ b/web/formfields.py Thu May 28 20:07:18 2009 +0200
@@ -20,6 +20,7 @@
HiddenInput, TextInput, FileInput, PasswordInput, TextArea, FCKEditor,
Radio, Select, DateTimePicker)
+
class Field(object):
"""field class is introduced to control what's displayed in forms. It makes
the link between something to edit and its display in the form. Actual
@@ -80,16 +81,13 @@
self.label = label or name
self.help = help
self.required = required
- if widget is not None:
- self.widget = widget
- if isinstance(self.widget, type):
- self.widget = self.widget()
self.initial = initial
self.choices = choices
self.sort = sort
self.internationalizable = internationalizable
self.eidparam = eidparam
self.role = role
+ self.init_widget(widget)
# ordering number for this field instance
self.creation_rank = Field.__creation_rank
Field.__creation_rank += 1
@@ -102,6 +100,14 @@
def __repr__(self):
return self.__unicode__().encode('utf-8')
+ def init_widget(self, widget):
+ if widget is None and self.choices:
+ widget = Select()
+ if widget is not None:
+ self.widget = widget
+ if isinstance(self.widget, type):
+ self.widget = self.widget()
+
def set_name(self, name):
"""automatically set .id and .label when name is set"""
assert name
@@ -177,10 +183,21 @@
"""
pass
+
class StringField(Field):
+ widget = TextArea
+
def __init__(self, max_length=None, **kwargs):
+ self.max_length = max_length # must be set before super call
super(StringField, self).__init__(**kwargs)
- self.max_length = max_length
+
+ def init_widget(self, widget):
+ if widget is None:
+ if self.choices:
+ widget = Select()
+ elif self.max_length and self.max_length < 257:
+ widget = TextInput()
+ super(StringField, self).init_widget(widget)
if isinstance(self.widget, TextArea):
self.init_text_area(self.widget)
@@ -223,7 +240,6 @@
else:
# else we want a format selector
fkwargs['widget'] = Select()
- fkwargs['widget'].attrs['size'] = 1
fcstr = FormatConstraint()
fkwargs['choices'] = fcstr.vocabulary(req=req)
fkwargs['internationalizable'] = True
@@ -342,6 +358,7 @@
self.widget.attrs.setdefault('size', 5)
self.widget.attrs.setdefault('maxlength', 15)
+
class BooleanField(Field):
widget = Radio
@@ -381,6 +398,7 @@
format_prop = 'ui.datetime-format'
widget = TextInput
+
class HiddenInitialValueField(Field):
def __init__(self, visible_field):
name = 'edit%s-%s' % (visible_field.role[0], visible_field.name)
@@ -470,18 +488,11 @@
# init StringField parameters according to constraints
for cstr in constraints:
if isinstance(cstr, StaticVocabularyConstraint):
- kwargs.setdefault('widget', Select())
kwargs.setdefault('choices', cstr.vocabulary)
- if card in '?1':
- if isinstance(kwargs['widget'], type):
- kwargs['widget'] = kwargs['widget']()
- kwargs['widget'].attrs.setdefault('size', 1)
+ break
for cstr in constraints:
if isinstance(cstr, SizeConstraint) and cstr.max is not None:
- if cstr.max < 257:
- kwargs.setdefault('widget', TextInput())
kwargs['max_length'] = cstr.max
- kwargs.setdefault('widget', TextArea())
return StringField(**kwargs)
if fieldclass is FileField:
for metadata in ('format', 'encoding'):
@@ -493,6 +504,7 @@
kwargs['role'] = role
return RelationField.fromcardinality(card, **kwargs)
+
FIELDS = {
'Boolean': BooleanField,
'Bytes': FileField,
--- a/web/formrenderers.py Thu May 28 19:07:41 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'<div id="progress">%s</div>' % form.req._('validating...'))
- w(u'<fieldset>')
- 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'</fieldset>')
- w(u'</form>')
- 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('<div class="helper">%s</div>' % form.req._(descr))
- example = field.example_format(form.req)
- if example:
- help.append('<div class="helper">(%s: %s)</div>'
- % (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 = '<li>%s</li>\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 = '<ul>%s</ul>' % errormsg
- return u'<div class="errorMessage">%s</div>' % 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 = ('<form action="%s" method="post" enctype="%s"' % (
- html_escape(action or '#'), enctype))
- if form.domid:
- tag += ' id="%s"' % form.domid
- if form.onsubmit:
- tag += ' onsubmit="%s"' % html_escape(form.onsubmit % dictattr(form))
- if form.cssstyle:
- tag += ' style="%s"' % html_escape(form.cssstyle)
- if form.cssclass:
- tag += ' class="%s"' % html_escape(form.cssclass)
- if form.cwtarget:
- tag += ' cubicweb:target="%s"' % html_escape(form.cwtarget)
- return 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'<table class="%s">' % self.table_class)
- for field in fields:
- w(u'<tr>')
- if self.display_label:
- w(u'<th class="labelCol">%s</th>' % self.render_label(form, field))
- error = form.form_field_error(field)
- if error:
- w(u'<td class="error">')
- w(error)
- else:
- w(u'<td>')
- w(field.render(form, self))
- if self.display_help:
- w(self.render_help(form, field))
- w(u'</td></tr>')
- w(u'</table>')
-
- def render_buttons(self, w, form):
- 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>')
-
-
-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'<table border="0">')
- 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.form_field_error(field)
- if error:
- w(u'<td class="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):
- """specific renderer for multiple entities edition form (muledit)"""
-
- def render_fields(self, w, form, values):
- if not form.is_subform:
- w(u'<table class="listing">')
- super(EntityCompositeFormRenderer, self).render_fields(w, form, values)
- if not form.is_subform:
- w(u'</table>')
-
- 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'<tr class="%s">' % (entity.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.form_field_error(field)
- if error:
- w(u'<td class="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
- w(u'<div>%s</div>' % field.render(form, self))
- w(u'</td>')
- else:
- # main form, display table headers
- w(u'<tr class="header">')
- w(u'<th align="left">%s</th>'
- % 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'<th>%s</th>' % form.req._(field.label))
- w(u'</tr>')
-
-
-
-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'</div>' # close extra div introducted by open_form
-
- def open_form(self, form, values):
- attrs_fs_label = ('<div class="iformTitle"><span>%s</span></div>'
- % form.req._('main informations'))
- attrs_fs_label += '<div class="formBody">'
- 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("""<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)
-
- 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'<fieldset class="subentity">')
- w(u'<legend class="iformTitle">%s</legend>' % label)
- w(u'<table id="relatedEntities">')
- for rschema, target, related in form.relations_table():
- # already linked entities
- if related:
- w(u'<tr><th class="labelCol">%s</th>' % rschema.display_name(req, target))
- w(u'<td>')
- w(u'<ul>')
- for viewparams in related:
- w(u'<li class="invisible">%s<div id="span%s" class="%s">%s</div></li>'
- % (viewparams[1], viewparams[0], viewparams[2], viewparams[3]))
- if not form.force_display and form.maxrelitems < len(related):
- link = (u'<span class="invisible">'
- '[<a href="javascript: window.location.href+=\'&__force_display=1\'">%s</a>]'
- '</span>' % form.req._('view all'))
- w(u'<li class="invisible">%s</li>' % link)
- w(u'</ul>')
- w(u'</td>')
- w(u'</tr>')
- pendings = list(form.restore_pending_inserts())
- if not pendings:
- w(u'<tr><th> </th><td> </td></tr>')
- else:
- for row in pendings:
- # soon to be linked to entities
- w(u'<tr id="tr%s">' % row[1])
- w(u'<th>%s</th>' % row[3])
- w(u'<td>')
- w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
- (_('cancel this insert'), row[2]))
- w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
- % (row[1], row[4], html_escape(row[5])))
- w(u'</td>')
- w(u'</tr>')
- w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
- w(u'<th class="labelCol">')
- w(u'<span>%s</span>' % _('add relation'))
- w(u'<select id="relationSelector_%s" tabindex="%s" '
- 'onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
- % (eid, req.next_tabindex(), html_escape(dumps(eid))))
- w(u'<option value="">%s</option>' % _('select a relation'))
- for i18nrtype, rschema, target in srels_by_cat:
- # more entities to link to
- w(u'<option value="%s_%s">%s</option>' % (rschema, target, i18nrtype))
- w(u'</select>')
- w(u'</th>')
- w(u'<td id="unrelatedDivs_%s"></td>' % eid)
- w(u'</tr>')
- w(u'</table>')
- w(u'</fieldset>')
-
- 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'<div id="inline%sslot">' % 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'<div class="inlinedform" id="%s" cubicweb:limit="true">'
- % divid)
- js = "addInlineCreationForm('%s', '%s', '%s', '%s')" % (
- entity.eid, targettype, rschema, role)
- if card in '1?':
- js = "toggleVisibility('%s'); %s" % (divid, js)
- w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
- % (rschema, entity.eid, js, __('add a %s' % targettype)))
- w(u'</div>')
- w(u'<div class="trame_grise"> </div>')
- w(u'</div>')
-
-
-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'<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'], form.req._('click on the box to cancel the deletion')))
- w(u'<div class="iformBody">')
- values['removemsg'] = form.req.__('remove this %s' % form.edited_entity.e_schema)
- w(u'<div class="iformTitle"><span>%(title)s</span> '
- '#<span class="icounter">1</span> '
- '[<a href="javascript: %(removejs)s;noop();">%(removemsg)s</a>]</div>'
- % values)
- # cleanup values
- for key in ('title', 'removejs', 'removemsg'):
- values.pop(key)
- self.render_fields(w, form, values)
- w(u'</div></div>')
- return '\n'.join(data)
-
- def render_fields(self, w, form, values):
- form.form_build_context(values)
- w(u'<fieldset id="fs-%(divid)s">' % values)
- fields = self._render_hidden_fields(w, form)
- w(u'</fieldset>')
- w(u'<fieldset class="subentity">')
- if fields:
- self._render_fields(fields, w, form)
- self.render_child_forms(w, form, values)
- self.inline_entities_form(w, form)
- w(u'</fieldset>')
-
--- a/web/formwidgets.py Thu May 28 19:07:41 2009 +0200
+++ b/web/formwidgets.py Thu May 28 20:07:18 2009 +0200
@@ -177,8 +177,8 @@
def render(self, form, field):
name, curvalues, attrs = self._render_attrs(form, field)
- if not 'size' in attrs and self._multiple:
- attrs['size'] = '5'
+ if not 'size' in attrs:
+ attrs['size'] = self._multiple and '5' or '1'
options = []
optgroup_opened = False
for label, value in field.vocabulary(form):
--- a/web/test/unittest_form.py Thu May 28 19:07:41 2009 +0200
+++ b/web/test/unittest_form.py Thu May 28 20:07:18 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):
@@ -153,8 +153,8 @@
def test_filefield(self):
class FFForm(EntityFieldsForm):
- data = FileField(format_field=StringField(name='data_format'),
- encoding_field=StringField(name='data_encoding'))
+ data = FileField(format_field=StringField(name='data_format', max_length=50),
+ encoding_field=StringField(name='data_encoding', max_length=20))
file = self.add_entity('File', name=u"pouet.txt", data_encoding=u'UTF-8',
data=Binary('new widgets system'))
form = FFForm(self.req, redirect_path='perdu.com', entity=file)
@@ -162,8 +162,8 @@
'''<input id="data:%(eid)s" name="data:%(eid)s" tabindex="0" type="file" value=""/>
<a href="javascript: toggleVisibility('data:%(eid)s-advanced')" title="show advanced fields"><img src="http://testing.fr/cubicweb/data/puce_down.png" alt="show advanced fields"/></a>
<div id="data:%(eid)s-advanced" class="hidden">
-<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" name="data_format:%(eid)s" tabindex="1" type="text" value="text/plain"/><br/><br/>
-<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" name="data_encoding:%(eid)s" tabindex="2" type="text" value="UTF-8"/><br/><br/>
+<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" name="data_format:%(eid)s" tabindex="1" type="text" value="text/plain"/><br/>
+<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" name="data_encoding:%(eid)s" tabindex="2" type="text" value="UTF-8"/><br/>
</div>
<br/>
<input name="data:%(eid)s__detach" type="checkbox"/>
@@ -173,8 +173,8 @@
def test_editablefilefield(self):
class EFFForm(EntityFieldsForm):
- data = EditableFileField(format_field=StringField(name='data_format'),
- encoding_field=StringField(name='data_encoding'))
+ data = EditableFileField(format_field=StringField(name='data_format', max_length=50),
+ encoding_field=StringField(name='data_encoding', max_length=20))
def form_field_encoding(self, field):
return 'ascii'
def form_field_format(self, field):
@@ -186,8 +186,8 @@
'''<input id="data:%(eid)s" name="data:%(eid)s" tabindex="0" type="file" value=""/>
<a href="javascript: toggleVisibility('data:%(eid)s-advanced')" title="show advanced fields"><img src="http://testing.fr/cubicweb/data/puce_down.png" alt="show advanced fields"/></a>
<div id="data:%(eid)s-advanced" class="hidden">
-<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" name="data_format:%(eid)s" tabindex="1" type="text" value="text/plain"/><br/><br/>
-<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" name="data_encoding:%(eid)s" tabindex="2" type="text" value="UTF-8"/><br/><br/>
+<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" name="data_format:%(eid)s" tabindex="1" type="text" value="text/plain"/><br/>
+<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" name="data_encoding:%(eid)s" tabindex="2" type="text" value="UTF-8"/><br/>
</div>
<br/>
<input name="data:%(eid)s__detach" type="checkbox"/>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/unittest_views_pyviews.py Thu May 28 20:07:18 2009 +0200
@@ -0,0 +1,25 @@
+from logilab.common.testlib import unittest_main
+from cubicweb.devtools.apptest import EnvBasedTC
+
+class PyViewsTC(EnvBasedTC):
+
+ def test_pyvaltable(self):
+ content = self.vreg.view('pyvaltable', self.request(),
+ pyvalue=[[1, 'a'], [2, 'b']],
+ headers=['num', 'char'])
+ self.assertEquals(content.strip(), '''<table class="listing">
+<tr><th>num</th><th>char</th></tr>
+<tr><td>1</td><td>a</td></tr>
+<tr><td>2</td><td>b</td></tr>
+</table>''')
+
+ def test_pyvallist(self):
+ content = self.vreg.view('pyvallist', self.request(),
+ pyvalue=[1, 'a'])
+ self.assertEquals(content.strip(), '''<ul>
+<li>1</li>
+<li>a</li>
+</ul>''')
+
+if __name__ == '__main__':
+ unittest_main()
--- a/web/views/autoform.py Thu May 28 19:07:41 2009 +0200
+++ b/web/views/autoform.py Thu May 28 20:07:18 2009 +0200
@@ -33,7 +33,7 @@
cwtarget = 'eformframe'
cssclass = 'entityForm'
copy_nav_params = True
- form_buttons = [SubmitButton(stdmsgs.BUTTON_OK),
+ form_buttons = [SubmitButton(),
Button(stdmsgs.BUTTON_APPLY, cwaction='apply'),
Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
attrcategories = ('primary', 'secondary')
--- a/web/views/basecontrollers.py Thu May 28 19:07:41 2009 +0200
+++ b/web/views/basecontrollers.py Thu May 28 20:07:18 2009 +0200
@@ -23,9 +23,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)
@@ -341,7 +341,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)
--- a/web/views/cwproperties.py Thu May 28 19:07:41 2009 +0200
+++ b/web/views/cwproperties.py Thu May 28 20:07:18 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 = '<div class="formsg"></div>'
--- a/web/views/editforms.py Thu May 28 19:07:41 2009 +0200
+++ b/web/views/editforms.py Thu May 28 20:07:18 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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/formrenderers.py Thu May 28 20:07:18 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'<div id="progress">%s</div>' % form.req._('validating...'))
+ w(u'<fieldset>')
+ 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'</fieldset>')
+ w(u'</form>')
+ 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('<div class="helper">%s</div>' % form.req._(descr))
+ example = field.example_format(form.req)
+ if example:
+ help.append('<div class="helper">(%s: %s)</div>'
+ % (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 = '<li>%s</li>\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 = '<ul>%s</ul>' % errormsg
+ return u'<div class="errorMessage">%s</div>' % 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 = ('<form action="%s" method="post" enctype="%s"' % (
+ html_escape(action or '#'), enctype))
+ if form.domid:
+ tag += ' id="%s"' % form.domid
+ if form.onsubmit:
+ tag += ' onsubmit="%s"' % html_escape(form.onsubmit % dictattr(form))
+ if form.cssstyle:
+ tag += ' style="%s"' % html_escape(form.cssstyle)
+ if form.cssclass:
+ tag += ' class="%s"' % html_escape(form.cssclass)
+ if form.cwtarget:
+ tag += ' cubicweb:target="%s"' % html_escape(form.cwtarget)
+ return 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'<table class="%s">' % self.table_class)
+ for field in fields:
+ w(u'<tr>')
+ if self.display_label:
+ w(u'<th class="labelCol">%s</th>' % self.render_label(form, field))
+ error = form.form_field_error(field)
+ if error:
+ w(u'<td class="error">')
+ w(error)
+ else:
+ w(u'<td>')
+ w(field.render(form, self))
+ if self.display_help:
+ w(self.render_help(form, field))
+ w(u'</td></tr>')
+ w(u'</table>')
+
+ def render_buttons(self, w, form):
+ 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>')
+
+
+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'<table border="0">')
+ 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.form_field_error(field)
+ if error:
+ w(u'<td class="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):
+ """specific renderer for multiple entities edition form (muledit)"""
+ id = 'composite'
+
+ def render_fields(self, w, form, values):
+ if not form.is_subform:
+ w(u'<table class="listing">')
+ super(EntityCompositeFormRenderer, self).render_fields(w, form, values)
+ if not form.is_subform:
+ w(u'</table>')
+
+ 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'<tr class="%s">' % (entity.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.form_field_error(field)
+ if error:
+ w(u'<td class="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
+ w(u'<div>%s</div>' % field.render(form, self))
+ w(u'</td>')
+ else:
+ # main form, display table headers
+ w(u'<tr class="header">')
+ w(u'<th align="left">%s</th>'
+ % 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'<th>%s</th>' % form.req._(field.label))
+ w(u'</tr>')
+
+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'</div>' # close extra div introducted by open_form
+
+ def open_form(self, form, values):
+ attrs_fs_label = ('<div class="iformTitle"><span>%s</span></div>'
+ % form.req._('main informations'))
+ attrs_fs_label += '<div class="formBody">'
+ 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("""<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)
+
+ 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'<fieldset class="subentity">')
+ w(u'<legend class="iformTitle">%s</legend>' % label)
+ w(u'<table id="relatedEntities">')
+ for rschema, target, related in form.relations_table():
+ # already linked entities
+ if related:
+ w(u'<tr><th class="labelCol">%s</th>' % rschema.display_name(req, target))
+ w(u'<td>')
+ w(u'<ul>')
+ for viewparams in related:
+ w(u'<li class="invisible">%s<div id="span%s" class="%s">%s</div></li>'
+ % (viewparams[1], viewparams[0], viewparams[2], viewparams[3]))
+ if not form.force_display and form.maxrelitems < len(related):
+ link = (u'<span class="invisible">'
+ '[<a href="javascript: window.location.href+=\'&__force_display=1\'">%s</a>]'
+ '</span>' % form.req._('view all'))
+ w(u'<li class="invisible">%s</li>' % link)
+ w(u'</ul>')
+ w(u'</td>')
+ w(u'</tr>')
+ pendings = list(form.restore_pending_inserts())
+ if not pendings:
+ w(u'<tr><th> </th><td> </td></tr>')
+ else:
+ for row in pendings:
+ # soon to be linked to entities
+ w(u'<tr id="tr%s">' % row[1])
+ w(u'<th>%s</th>' % row[3])
+ w(u'<td>')
+ w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
+ (_('cancel this insert'), row[2]))
+ w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
+ % (row[1], row[4], html_escape(row[5])))
+ w(u'</td>')
+ w(u'</tr>')
+ w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
+ w(u'<th class="labelCol">')
+ w(u'<span>%s</span>' % _('add relation'))
+ w(u'<select id="relationSelector_%s" tabindex="%s" '
+ 'onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
+ % (eid, req.next_tabindex(), html_escape(dumps(eid))))
+ w(u'<option value="">%s</option>' % _('select a relation'))
+ for i18nrtype, rschema, target in srels_by_cat:
+ # more entities to link to
+ w(u'<option value="%s_%s">%s</option>' % (rschema, target, i18nrtype))
+ w(u'</select>')
+ w(u'</th>')
+ w(u'<td id="unrelatedDivs_%s"></td>' % eid)
+ w(u'</tr>')
+ w(u'</table>')
+ w(u'</fieldset>')
+
+ 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'<div id="inline%sslot">' % 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'<div class="inlinedform" id="%s" cubicweb:limit="true">'
+ % divid)
+ js = "addInlineCreationForm('%s', '%s', '%s', '%s')" % (
+ entity.eid, targettype, rschema, role)
+ if card in '1?':
+ js = "toggleVisibility('%s'); %s" % (divid, js)
+ w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
+ % (rschema, entity.eid, js, __('add a %s' % targettype)))
+ w(u'</div>')
+ w(u'<div class="trame_grise"> </div>')
+ w(u'</div>')
+
+
+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'<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'], form.req._('click on the box to cancel the deletion')))
+ w(u'<div class="iformBody">')
+ values['removemsg'] = form.req.__('remove this %s' % form.edited_entity.e_schema)
+ w(u'<div class="iformTitle"><span>%(title)s</span> '
+ '#<span class="icounter">1</span> '
+ '[<a href="javascript: %(removejs)s;noop();">%(removemsg)s</a>]</div>'
+ % values)
+ # cleanup values
+ for key in ('title', 'removejs', 'removemsg'):
+ values.pop(key)
+ self.render_fields(w, form, values)
+ w(u'</div></div>')
+ return '\n'.join(data)
+
+ def render_fields(self, w, form, values):
+ form.form_build_context(values)
+ w(u'<fieldset id="fs-%(divid)s">' % values)
+ fields = self._render_hidden_fields(w, form)
+ w(u'</fieldset>')
+ w(u'<fieldset class="subentity">')
+ if fields:
+ self._render_fields(fields, w, form)
+ self.render_child_forms(w, form, values)
+ self.inline_entities_form(w, form)
+ w(u'</fieldset>')
+
--- a/web/views/management.py Thu May 28 19:07:41 2009 +0200
+++ b/web/views/management.py Thu May 28 20:07:18 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):
--- a/web/views/massmailing.py Thu May 28 19:07:41 2009 +0200
+++ b/web/views/massmailing.py Thu May 28 20:07:18 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))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/pyviews.py Thu May 28 20:07:18 2009 +0200
@@ -0,0 +1,42 @@
+"""Views to display bare python values
+
+: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 cubicweb.view import View
+from cubicweb.selectors import match_kwargs
+
+class PyValTableView(View):
+ id = 'pyvaltable'
+ __select__ = match_kwargs('pyvalue')
+
+ def call(self, pyvalue, headers=None):
+ if headers is None:
+ headers = self.req.form.get('headers')
+ self.w(u'<table class="listing">\n')
+ if headers:
+ self.w(u'<tr>')
+ for header in headers:
+ self.w(u'<th>%s</th>' % header)
+ self.w(u'</tr>\n')
+ for row in pyvalue:
+ self.w(u'<tr>')
+ for cell in row:
+ self.w(u'<td>%s</td>' % cell)
+ self.w(u'</tr>\n')
+ self.w(u'</table>\n')
+
+
+class PyValListView(View):
+ id = 'pyvallist'
+ __select__ = match_kwargs('pyvalue')
+
+ def call(self, pyvalue):
+ self.w(u'<ul>\n')
+ for line in pyvalue:
+ self.w(u'<li>%s</li>\n' % line)
+ self.w(u'</ul>\n')
--- a/web/views/workflow.py Thu May 28 19:07:41 2009 +0200
+++ b/web/views/workflow.py Thu May 28 20:07:18 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):