merge
authorFlorent <florent@secondweb.fr>
Tue, 28 Jul 2009 17:36:30 +0200
changeset 2553 1abdcb4199af
parent 2530 3cd117ecc3a8 (diff)
parent 2552 1ea2f2ff5dca (current diff)
child 2554 3b6a6d2f9d7e
merge
--- 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),
                   '&nbsp;', 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">&nbsp;</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__)