--- a/common/tags.py Tue Jul 21 19:40:10 2009 +0200
+++ b/common/tags.py Tue Jul 28 17:36:30 2009 +0200
@@ -7,7 +7,7 @@
"""
__docformat__ = "restructuredtext en"
-from cubicweb.common.uilib import simple_sgml_tag
+from cubicweb.common.uilib import simple_sgml_tag, sgml_attributes
class tag(object):
def __init__(self, name, escapecontent=True):
@@ -38,8 +38,7 @@
if id:
attrs['id'] = id
attrs['name'] = name
- html = [u'<select %s>' % ' '.join('%s="%s"' % kv
- for kv in sorted(attrs.items()))]
+ html = [u'<select %s>' % sgml_attributes(attrs)]
html += options
html.append(u'</select>')
return u'\n'.join(html)
--- a/common/uilib.py Tue Jul 21 19:40:10 2009 +0200
+++ b/common/uilib.py Tue Jul 28 17:36:30 2009 +0200
@@ -212,6 +212,11 @@
HTML4_EMPTY_TAGS = frozenset(('base', 'meta', 'link', 'hr', 'br', 'param',
'img', 'area', 'input', 'col'))
+def sgml_attributes(attrs):
+ return u' '.join(u'%s="%s"' % (attr, xml_escape(unicode(value)))
+ for attr, value in sorted(attrs.items())
+ if value is not None)
+
def simple_sgml_tag(tag, content=None, escapecontent=True, **attrs):
"""generation of a simple sgml tag (eg without children tags) easier
@@ -223,9 +228,7 @@
attrs['class'] = attrs.pop('klass')
except KeyError:
pass
- value += u' ' + u' '.join(u'%s="%s"' % (attr, xml_escape(unicode(value)))
- for attr, value in sorted(attrs.items())
- if value is not None)
+ value += u' ' + sgml_attributes(attrs)
if content:
if escapecontent:
content = xml_escape(unicode(content))
--- a/debian/control Tue Jul 21 19:40:10 2009 +0200
+++ b/debian/control Tue Jul 28 17:36:30 2009 +0200
@@ -62,7 +62,7 @@
Architecture: all
XB-Python-Version: ${python:Versions}
Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), python-simplejson (>= 1.3), python-elementtree
-Recommends: python-docutils, python-vobject, fckeditor
+Recommends: python-docutils, python-vobject, fckeditor, python-fyzz
Description: web interface library for the CubicWeb framework
CubicWeb is a semantic web application framework.
.
--- a/devtools/devctl.py Tue Jul 21 19:40:10 2009 +0200
+++ b/devtools/devctl.py Tue Jul 28 17:36:30 2009 +0200
@@ -327,8 +327,8 @@
def update_cubes_catalogs(cubes):
- toedit = []
for cubedir in cubes:
+ toedit = []
if not isdir(cubedir):
print '-> ignoring %s that is not a directory.' % cubedir
continue
@@ -337,12 +337,14 @@
except Exception:
import traceback
traceback.print_exc()
- print '-> Error while updating catalogs for cube', cubedir
- # instructions pour la suite
- print '-> regenerated this cube\'s .po catalogs.'
- print '\nYou can now edit the following files:'
- print '* ' + '\n* '.join(toedit)
- print 'when you are done, run "cubicweb-ctl i18ninstance yourinstance".'
+ print '-> error while updating catalogs for cube', cubedir
+ else:
+ # instructions pour la suite
+ print '-> regenerated .po catalogs for cube %s.' % cubedir
+ print '\nYou can now edit the following files:'
+ print '* ' + '\n* '.join(toedit)
+ print ('When you are done, run "cubicweb-ctl i18ninstance '
+ '<yourinstance>" to see changes in your instances.')
def update_cube_catalogs(cubedir):
import shutil
--- a/schema.py Tue Jul 21 19:40:10 2009 +0200
+++ b/schema.py Tue Jul 28 17:36:30 2009 +0200
@@ -21,7 +21,7 @@
from yams.schema import Schema, ERSchema, EntitySchema, RelationSchema
from yams.constraints import BaseConstraint, StaticVocabularyConstraint
from yams.reader import CONSTRAINTS, PyFileReader, SchemaLoader, \
- obsolete as yobsolete
+ obsolete as yobsolete, cleanup_sys_modules
from rql import parse, nodes, RQLSyntaxError, TypeResolverException
@@ -868,7 +868,11 @@
path = reversed([config.apphome] + config.cubes_path())
else:
path = reversed(config.cubes_path())
- return super(CubicWebSchemaLoader, self).load(config, path=path, **kwargs)
+ try:
+ return super(CubicWebSchemaLoader, self).load(config, path=path, **kwargs)
+ finally:
+ # we've to cleanup modules imported from cubicweb.schemas as well
+ cleanup_sys_modules([self.lib_directory])
def _load_definition_files(self, cubes):
for filepath in (join(self.lib_directory, 'bootstrap.py'),
--- a/selectors.py Tue Jul 21 19:40:10 2009 +0200
+++ b/selectors.py Tue Jul 28 17:36:30 2009 +0200
@@ -616,10 +616,10 @@
@lltrace
def __call__(self, cls, req, *args, **kwargs):
try:
- etype = req.form['etype']
+ etype = kwargs['etype']
except KeyError:
try:
- etype = kwargs['etype']
+ etype = req.form['etype']
except KeyError:
return 0
else:
--- a/web/formfields.py Tue Jul 21 19:40:10 2009 +0200
+++ b/web/formfields.py Tue Jul 28 17:36:30 2009 +0200
@@ -63,6 +63,8 @@
:role:
when the field is linked to an entity attribute or relation, tells the
role of the entity in the relation (eg 'subject' or 'object')
+ :fieldset:
+ optional fieldset to which this field belongs to
"""
# default widget associated to this class of fields. May be overriden per
@@ -76,7 +78,7 @@
def __init__(self, name=None, id=None, label=None, help=None,
widget=None, required=False, initial=None,
choices=None, sort=True, internationalizable=False,
- eidparam=False, role='subject'):
+ eidparam=False, role='subject', fieldset=None):
self.name = name
self.id = id or name
self.label = label or name
@@ -88,6 +90,7 @@
self.internationalizable = internationalizable
self.eidparam = eidparam
self.role = role
+ self.fieldset = fieldset
self.init_widget(widget)
# ordering number for this field instance
self.creation_rank = Field.__creation_rank
@@ -158,7 +161,13 @@
"""render this field, which is part of form, using the given form
renderer
"""
- return self.get_widget(form).render(form, self)
+ widget = self.get_widget(form)
+ try:
+ return widget.render(form, self, renderer)
+ except TypeError:
+ warn('widget.render now take the renderer as third argument, please update %s implementation'
+ % widget.__class__.__name__, DeprecationWarning)
+ return widget.render(form, self)
def vocabulary(self, form):
"""return vocabulary for this field. This method will be called by
@@ -287,7 +296,7 @@
result = format_field.render(form, renderer)
else:
result = u''
- return result + self.get_widget(form).render(form, self)
+ return result + self.get_widget(form).render(form, self, renderer)
class FileField(StringField):
@@ -307,7 +316,7 @@
yield self.encoding_field
def render(self, form, renderer):
- wdgs = [self.get_widget(form).render(form, self)]
+ wdgs = [self.get_widget(form).render(form, self, renderer)]
if self.format_field or self.encoding_field:
divid = '%s-advanced' % form.context[self]['name']
wdgs.append(u'<a href="%s" title="%s"><img src="%s" alt="%s"/></a>' %
@@ -361,7 +370,7 @@
'You can either submit a new file using the browse button above'
', or edit file content online with the widget below.')
wdgs.append(u'<p><b>%s</b></p>' % msg)
- wdgs.append(TextArea(setdomid=False).render(form, self))
+ wdgs.append(TextArea(setdomid=False).render(form, self, renderer))
# XXX restore form context?
return '\n'.join(wdgs)
@@ -464,6 +473,15 @@
return value
+class CompoundField(Field):
+ def __init__(self, fields, *args, **kwargs):
+ super(CompoundField, self).__init__(*args, **kwargs)
+ self.fields = fields
+
+ def actual_fields(self, form):
+ return [self] + list(self.fields)
+
+
def guess_field(eschema, rschema, role='subject', skip_meta_attr=True, **kwargs):
"""return the most adapated widget to edit the relation
'subjschema rschema objschema' according to information found in the schema
--- a/web/formwidgets.py Tue Jul 21 19:40:10 2009 +0200
+++ b/web/formwidgets.py Tue Jul 28 17:36:30 2009 +0200
@@ -10,7 +10,7 @@
from datetime import date
from warnings import warn
-from cubicweb.common import tags
+from cubicweb.common import tags, uilib
from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE
@@ -43,7 +43,7 @@
if self.needs_css:
form.req.add_css(self.needs_css)
- def render(self, form, field):
+ def render(self, form, field, renderer):
"""render the widget for the given `field` of `form`.
To override in concrete class
"""
@@ -68,7 +68,7 @@
"""abstract widget class for <input> tag based widgets"""
type = None
- def render(self, form, field):
+ def render(self, form, field, renderer):
"""render the widget for the given `field` of `form`.
Generate one <input> tag for each field's value
@@ -96,7 +96,7 @@
"""
type = 'password'
- def render(self, form, field):
+ def render(self, form, field, renderer):
self.add_media(form)
name, values, attrs = self._render_attrs(form, field)
assert len(values) == 1
@@ -105,9 +105,11 @@
confirmname = '%s-confirm:%s' % tuple(name.rsplit(':', 1))
except TypeError:
confirmname = '%s-confirm' % name
- inputs = [tags.input(name=name, value=values[0], type=self.type, id=id, **attrs),
+ inputs = [tags.input(name=name, value=values[0], type=self.type, id=id,
+ **attrs),
'<br/>',
- tags.input(name=confirmname, value=values[0], type=self.type, **attrs),
+ tags.input(name=confirmname, value=values[0], type=self.type,
+ **attrs),
' ', tags.span(form.req._('confirm password'),
**{'class': 'emphasis'})]
return u'\n'.join(inputs)
@@ -147,7 +149,7 @@
class TextArea(FieldWidget):
"""<textarea>"""
- def render(self, form, field):
+ def render(self, form, field, renderer):
name, values, attrs = self._render_attrs(form, field)
attrs.setdefault('onkeyup', 'autogrow(this)')
if not values:
@@ -171,9 +173,9 @@
super(FCKEditor, self).__init__(*args, **kwargs)
self.attrs['cubicweb:type'] = 'wysiwyg'
- def render(self, form, field):
+ def render(self, form, field, renderer):
form.req.fckeditor_config()
- return super(FCKEditor, self).render(form, field)
+ return super(FCKEditor, self).render(form, field, renderer)
class Select(FieldWidget):
@@ -184,23 +186,30 @@
super(Select, self).__init__(attrs)
self._multiple = multiple
- def render(self, form, field):
+ def render(self, form, field, renderer):
name, curvalues, attrs = self._render_attrs(form, field)
if not 'size' in attrs:
attrs['size'] = self._multiple and '5' or '1'
options = []
optgroup_opened = False
- for label, value in field.vocabulary(form):
+ for option in field.vocabulary(form):
+ try:
+ label, value, oattrs = option
+ except ValueError:
+ label, value = option
+ oattrs = {}
if value is None:
# handle separator
if optgroup_opened:
options.append(u'</optgroup>')
- options.append(u'<optgroup label="%s">' % (label or ''))
+ oattrs.setdefault('label', label or '')
+ options.append(u'<optgroup %s>' % uilib.sgml_attributes(oattrs))
optgroup_opened = True
elif value in curvalues:
- options.append(tags.option(label, value=value, selected='selected'))
+ options.append(tags.option(label, value=value,
+ selected='selected', **oattrs))
else:
- options.append(tags.option(label, value=value))
+ options.append(tags.option(label, value=value, **oattrs))
if optgroup_opened:
options.append(u'</optgroup>')
return tags.select(name=name, multiple=self._multiple,
@@ -214,20 +223,26 @@
type = 'checkbox'
vocabulary_widget = True
- def render(self, form, field):
+ def render(self, form, field, renderer):
name, curvalues, attrs = self._render_attrs(form, field)
domid = attrs.pop('id', None)
- sep = attrs.pop('separator', u'<br/>')
+ sep = attrs.pop('separator', u'<br/>\n')
options = []
- for i, (label, value) in enumerate(field.vocabulary(form)):
+ for i, option in enumerate(field.vocabulary(form)):
+ try:
+ label, value, oattrs = option
+ except ValueError:
+ label, value = option
+ oattrs = {}
iattrs = attrs.copy()
+ iattrs.update(oattrs)
if i == 0 and domid is not None:
- iattrs['id'] = domid
+ iattrs.setdefault('id', domid)
if value in curvalues:
iattrs['checked'] = u'checked'
tag = tags.input(name=name, type=self.type, value=value, **iattrs)
- options.append(tag + label + sep)
- return '\n'.join(options)
+ options.append(tag + label)
+ return sep.join(options)
class Radio(CheckBox):
@@ -237,6 +252,51 @@
type = 'radio'
+# compound widgets #############################################################
+
+class IntervalWidget(FieldWidget):
+ """custom widget to display an interval composed by 2 fields. This widget
+ is expected to be used with a CompoundField containing the two actual
+ fields.
+
+ Exemple usage::
+
+from uicfg import autoform_field, autoform_section
+autoform_field.tag_attribute(('Concert', 'minprice'),
+ CompoundField(fields=(IntField(name='minprice'),
+ IntField(name='maxprice')),
+ label=_('price'),
+ widget=IntervalWidget()
+ ))
+# we've to hide the other field manually for now
+autoform_section.tag_attribute(('Concert', 'maxprice'), 'generated')
+ """
+ def render(self, form, field, renderer):
+ actual_fields = field.fields
+ assert len(actual_fields) == 2
+ return u'<div>%s %s %s %s</div>' % (
+ form.req._('from_interval_start'),
+ actual_fields[0].render(form, renderer),
+ form.req._('to_interval_end'),
+ actual_fields[1].render(form, renderer),
+ )
+
+
+class HorizontalLayoutWidget(FieldWidget):
+ """custom widget to display a set of fields grouped together horizontally
+ in a form. See `IntervalWidget` for example usage.
+ """
+ def render(self, form, field, renderer):
+ if self.attrs.get('display_label', True):
+ subst = self.attrs.get('label_input_substitution', '%(label)s %(input)s')
+ fields = [subst % {'label': renderer.render_label(form, f),
+ 'input': f.render(form, renderer)}
+ for f in field.fields]
+ else:
+ fields = [f.render(form, renderer) for f in field.fields]
+ return u'<div>%s</div>' % ' '.join(fields)
+
+
# javascript widgets ###########################################################
class DateTimePicker(TextInput):
@@ -262,8 +322,8 @@
req.html_headers.define_var('MONTHNAMES', monthnames)
req.html_headers.define_var('DAYNAMES', daynames)
- def render(self, form, field):
- txtwidget = super(DateTimePicker, self).render(form, field)
+ def render(self, form, field, renderer):
+ txtwidget = super(DateTimePicker, self).render(form, field, renderer)
self.add_localized_infos(form.req)
cal_button = self._render_calendar_popup(form, field)
return txtwidget + cal_button
@@ -302,7 +362,7 @@
if inputid is not None:
self.attrs['cubicweb:inputid'] = inputid
- def render(self, form, field):
+ def render(self, form, field, renderer):
self.add_media(form)
attrs = self._render_attrs(form, field)[-1]
return tags.div(**attrs)
@@ -373,8 +433,8 @@
etype_from = entity.e_schema.subject_relation(self.name).objects(entity.e_schema)[0]
attrs['cubicweb:etype_from'] = etype_from
- def render(self, form, field):
- return super(AddComboBoxWidget, self).render(form, field) + u'''
+ def render(self, form, field, renderer):
+ return super(AddComboBoxWidget, self).render(form, field, renderer) + u'''
<div id="newvalue">
<input type="text" id="newopt" />
<a href="javascript:noop()" id="add_newopt"> </a></div>
@@ -400,7 +460,7 @@
self.cwaction = cwaction
self.attrs.setdefault('klass', 'validateButton')
- def render(self, form, field=None):
+ def render(self, form, field=None, renderer=None):
label = form.req._(self.label)
attrs = self.attrs.copy()
if self.cwaction:
@@ -443,7 +503,7 @@
self.imgressource = imgressource
self.label = label
- def render(self, form, field=None):
+ def render(self, form, field=None, renderer=None):
label = form.req._(self.label)
imgsrc = form.req.external_resource(self.imgressource)
return '<a id="%(domid)s" href="%(href)s">'\
--- a/web/uicfg.py Tue Jul 21 19:40:10 2009 +0200
+++ b/web/uicfg.py Tue Jul 28 17:36:30 2009 +0200
@@ -113,9 +113,9 @@
primaryview_section.tag_subject_of(('*', 'use_email', '*'), 'attributes')
primaryview_section.tag_subject_of(('*', 'primary_email', '*'), 'hidden')
-for attr in ('name', 'meta', 'final'):
+for attr in ('name', 'final'):
primaryview_section.tag_attribute(('CWEType', attr), 'hidden')
-for attr in ('name', 'meta', 'final', 'symetric', 'inlined'):
+for attr in ('name', 'final', 'symetric', 'inlined'):
primaryview_section.tag_attribute(('CWRType', attr), 'hidden')
--- a/web/views/formrenderers.py Tue Jul 21 19:40:10 2009 +0200
+++ b/web/views/formrenderers.py Tue Jul 28 17:36:30 2009 +0200
@@ -85,6 +85,8 @@
return '\n'.join(data)
def render_label(self, form, field):
+ if field.label is None:
+ return u''
label = self.req._(field.label)
attrs = {'for': form.context[field]['id']}
if field.required:
@@ -188,22 +190,29 @@
return fields
def _render_fields(self, fields, w, form):
- w(u'<table class="%s">' % self.table_class)
+ byfieldset = {}
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>')
+ byfieldset.setdefault(field.fieldset, []).append(field)
+ for fieldset, fields in byfieldset.iteritems():
+ w(u'<fieldset class="%s">' % (fieldset or u'default'))
+ if fieldset:
+ w(u'<legend>%s</legend>' % self.req._(fieldset))
+ w(u'<table class="%s">' % self.table_class)
+ for field in fields:
+ w(u'<tr id="%s_%s">' % (field.name, field.role))
+ 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></fieldset>')
def render_buttons(self, w, form):
if not form.form_buttons:
--- a/web/views/idownloadable.py Tue Jul 21 19:40:10 2009 +0200
+++ b/web/views/idownloadable.py Tue Jul 28 17:36:30 2009 +0200
@@ -144,9 +144,21 @@
self.wview(self.id, rset, row=i, col=0)
self.w(u'</div>')
- def cell_call(self, row, col):
+ def cell_call(self, row, col, width=None, height=None, link=False):
entity = self.entity(row, col)
#if entity.data_format.startswith('image/'):
- self.w(u'<img src="%s" alt="%s"/>' % (xml_escape(entity.download_url()),
- xml_escape(entity.download_file_name())))
+ imgtag = u'<img src="%s" alt="%s" ' % (xml_escape(entity.download_url()),
+ xml_escape(entity.download_file_name()))
+ if width:
+ imgtag += u'width="%i" ' % width
+ if height:
+ imgtag += u'height="%i" ' % height
+ imgtag += u'/>'
+ if link:
+ self.w(u'<a href="%s" alt="%s">%s</a>' % (entity.absolute_url(vid='download'),
+ self.req._('download image'),
+ imgtag))
+ else:
+ self.w(imgtag)
+
--- a/web/views/sparql.py Tue Jul 21 19:40:10 2009 +0200
+++ b/web/views/sparql.py Tue Jul 28 17:36:30 2009 +0200
@@ -16,8 +16,11 @@
from cubicweb.view import StartupView, AnyRsetView
from cubicweb.web import Redirect, form, formfields, formwidgets as fwdgs
from cubicweb.web.views import forms, urlrewrite
-from cubicweb.spa2rql import Sparql2rqlTranslator
-
+try:
+ from cubicweb.spa2rql import Sparql2rqlTranslator
+except ImportError:
+ # fyzz not available (only a recommends)
+ Sparql2rqlTranslator = None
class SparqlForm(forms.FieldsForm):
id = 'sparql'
@@ -113,3 +116,7 @@
self.req.set_content_type(self.content_type,
filename='sparql.xml',
encoding=self.req.encoding)
+
+def registration_callback(vreg):
+ if Sparql2rqlTranslator is not None:
+ vreg.register_all(globals().values(), __name__)