merge and fix cubicwebSortValueExtraction pb which disappeared when tablesorter.js has been updated
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 14 Oct 2011 09:21:45 +0200
changeset 7953 a37531c8a4a6
parent 7951 b7c825b00f64 (current diff)
parent 7952 48330faf4cd7 (diff)
child 7954 a3d3220669d6
merge and fix cubicwebSortValueExtraction pb which disappeared when tablesorter.js has been updated
server/migractions.py
web/data/cubicweb.js
web/data/jquery.tablesorter.js
web/facet.py
web/views/actions.py
web/views/facets.py
web/views/tableview.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pylintext.py	Fri Oct 14 09:21:45 2011 +0200
@@ -0,0 +1,41 @@
+"""https://pastebin.logilab.fr/show/860/"""
+
+from logilab.astng import MANAGER, nodes, scoped_nodes
+
+def turn_function_to_class(node):
+    """turn a Function node into a Class node (in-place)"""
+    node.__class__ = scoped_nodes.Class
+    node.bases = ()
+    # remove return nodes so that we don't get warned about 'return outside
+    # function' by pylint
+    for rnode in node.nodes_of_class(nodes.Return):
+        rnode.parent.body.remove(rnode)
+    # that seems to be enough :)
+
+
+def cubicweb_transform(module):
+    # handle objectify_selector decorator. Only look at module level functions,
+    # should be enough
+    for assnodes in module.locals.values():
+        for node in assnodes:
+            if isinstance(node, scoped_nodes.Function) and node.decorators:
+                for decorator in node.decorators.nodes:
+                    for infered in decorator.infer():
+                        if infered.name == 'objectify_selector':
+                            turn_function_to_class(node)
+                            break
+                    else:
+                        continue
+                    break
+    # add yams base types into 'yams.buildobjs', astng doesn't grasp globals()
+    # magic in there
+    if module.name == 'yams.buildobjs':
+        from yams import BASE_TYPES
+        for etype in BASE_TYPES:
+            module.locals[etype] = [scoped_nodes.Class(etype, None)]
+
+MANAGER.register_transformer(cubicweb_transform)
+
+def register(linter):
+    """called when loaded by pylint --load-plugins, nothing to do here"""
+
--- a/server/migractions.py	Fri Oct 14 08:51:24 2011 +0200
+++ b/server/migractions.py	Fri Oct 14 09:21:45 2011 +0200
@@ -1554,14 +1554,15 @@
         rschema = self.repo.schema.rschema(attr)
         oldtype = rschema.objects(etype)[0]
         rdefeid = rschema.rdef(etype, oldtype).eid
+        allownull = rschema.rdef(etype, oldtype).cardinality[0] != '1'
         sql = ("UPDATE cw_CWAttribute "
                "SET cw_to_entity=(SELECT cw_eid FROM cw_CWEType WHERE cw_name='%s')"
                "WHERE cw_eid=%s") % (newtype, rdefeid)
         self.sqlexec(sql, ask_confirm=False)
         dbhelper = self.repo.system_source.dbhelper
         sqltype = dbhelper.TYPE_MAPPING[newtype]
-        sql = 'ALTER TABLE cw_%s ALTER COLUMN cw_%s TYPE %s' % (etype, attr, sqltype)
-        self.sqlexec(sql, ask_confirm=False)
+        cursor = self.session.cnxset[self.repo.system_source.uri]
+        dbhelper.change_col_type(cursor, 'cw_%s'  % etype, 'cw_%s' % attr, sqltype, allownull)
         if commit:
             self.commit()
 
--- a/view.py	Fri Oct 14 08:51:24 2011 +0200
+++ b/view.py	Fri Oct 14 09:21:45 2011 +0200
@@ -23,6 +23,7 @@
 import types, new
 from cStringIO import StringIO
 from warnings import warn
+from functools import partial
 
 from logilab.common.deprecation import deprecated
 from logilab.mtconverter import xml_escape
@@ -451,25 +452,54 @@
     category = _('anyrsetview')
 
     def columns_labels(self, mainindex=0, tr=True):
+        """compute the label of the rset colums
+
+        The logic is based on :meth:`~rql.stmts.Union.get_description`.
+
+        :param mainindex: The index of the main variable. This is an hint to get
+                          more accurate label for various situation
+        :type mainindex:  int
+
+        :param tr: Should the label be translated ?
+        :type tr: boolean
+        """
         if tr:
-            translate = lambda val, req=self._cw: display_name(req, val)
+            translate = partial(display_name, self._cw)
         else:
             translate = lambda val: val
         # XXX [0] because of missing Union support
-        rqlstdescr = self.cw_rset.syntax_tree().get_description(mainindex,
-                                                                translate)[0]
+        rql_syntax_tree = self.cw_rset.syntax_tree()
+        rqlstdescr = rql_syntax_tree.get_description(mainindex)[0]
         labels = []
         for colidx, label in enumerate(rqlstdescr):
-            try:
-                label = getattr(self, 'label_column_%s' % colidx)()
-            except AttributeError:
-                # compute column header
-                if label == 'Any': # find a better label
-                    label = ','.join(translate(et)
-                                     for et in self.cw_rset.column_types(colidx))
-            labels.append(label)
+            labels.append(self.column_label(colidx, label, translate))
         return labels
 
+    def column_label(self, colidx, default, translate_func=None):
+        """return the label of a specified columns index
+
+        Overwrite me if you need to compute specific label.
+
+        :param colidx: The index of the column the call computes a label for.
+        :type colidx:  int
+
+        :param default: Default value. If ``"Any"`` the default value will be
+                        recomputed as coma separated list for all possible
+                        etypes name.
+        :type colidx:  string
+
+        :param translate_func: A function used to translate name.
+        :type colidx:  function
+        """
+        label = default
+        if label == 'Any':
+            etypes = self.cw_rset.column_types(colidx)
+            if translate_func is not None:
+                etypes = map(translate_func, etypes)
+            label = ','.join(etypes)
+        return label
+
+
 
 # concrete template base classes ##############################################
 
--- a/web/data/cubicweb.ajax.js	Fri Oct 14 08:51:24 2011 +0200
+++ b/web/data/cubicweb.ajax.js	Fri Oct 14 09:21:45 2011 +0200
@@ -257,10 +257,6 @@
 }
 
 function _postAjaxLoad(node) {
-    // find sortable tables if there are some
-    if (typeof(Sortable) != 'undefined') {
-        Sortable.sortTables(node);
-    }
     // find textareas and wrap them if there are some
     if (typeof(FCKeditor) != 'undefined') {
         buildWysiwygEditors();
--- a/web/data/cubicweb.js	Fri Oct 14 08:51:24 2011 +0200
+++ b/web/data/cubicweb.js	Fri Oct 14 09:21:45 2011 +0200
@@ -82,6 +82,15 @@
         }
         return src;
     }
+
+
+    sortValueExtraction: function (node) {
+	var sortvalue = jQuery(node).attr('cubicweb:sortvalue');
+	if (sortvalue === undefined) {
+	    return '';
+	}
+	return sortvalue;
+}
 });
 
 
--- a/web/facet.py	Fri Oct 14 08:51:24 2011 +0200
+++ b/web/facet.py	Fri Oct 14 09:21:45 2011 +0200
@@ -90,6 +90,31 @@
 ## rqlst manipulation functions used by facets ################################
 
 def init_facets(rset, select, mainvar=None):
+    """Alters in place the <select> for filtering and returns related data.
+
+    Calls :func:`prepare_select` to prepare the syntaxtree for selection and
+    :func:`get_filtered_variable` that selects the variable to be filtered and
+    drops several parts of the select tree. See each function docstring for
+    details.
+
+    :param rset: ResultSet we init facet for.
+    :type rset: :class:`~cubicweb.rset.ResultSet`
+
+    :param select: Select statement to be *altered* to support filtering.
+    :type select:   :class:`~rql.stmts.Select` from the ``rset`` parameters.
+
+    :param mainvar: Name of the variable we want to filter with facets.
+    :type mainvar:  string
+
+    :rtype: (filtered_variable, baserql) tuple.
+    :return filtered_variable:  A rql class:`~rql.node.VariableRef`
+                                instance as returned by
+                                :func:`get_filtered_variable`.
+
+    :return baserql: A string containing the rql before
+                     :func:`prepare_select` but after
+                     :func:`get_filtered_variable`.
+    """
     rset.req.vreg.rqlhelper.annotate(select)
     filtered_variable = get_filtered_variable(select, mainvar)
     baserql = select.as_string(kwargs=rset.args) # before call to prepare_select
--- a/web/views/actions.py	Fri Oct 14 08:51:24 2011 +0200
+++ b/web/views/actions.py	Fri Oct 14 09:21:45 2011 +0200
@@ -147,7 +147,7 @@
 class ModifyAction(action.Action):
     __regid__ = 'edit'
     __select__ = (action.Action.__select__
-                  & one_line_rset() & has_editable_relation('add'))
+                  & one_line_rset() & has_editable_relation())
 
     title = _('modify')
     category = 'mainactions'
--- a/web/views/cwsources.py	Fri Oct 14 08:51:24 2011 +0200
+++ b/web/views/cwsources.py	Fri Oct 14 09:21:45 2011 +0200
@@ -39,7 +39,6 @@
 _abaa.tag_object_of(('CWSourceSchemaConfig', 'cw_host_config_of', '*'), False)
 
 _afs = uicfg.autoform_section
-_afs.tag_attribute(('CWSource', 'synchronizing'), 'main', 'hidden')
 _afs.tag_object_of(('*', 'cw_for_source', 'CWSource'), 'main', 'hidden')
 
 _affk = uicfg.autoform_field_kwargs
--- a/web/views/facets.py	Fri Oct 14 08:51:24 2011 +0200
+++ b/web/views/facets.py	Fri Oct 14 09:21:45 2011 +0200
@@ -27,6 +27,7 @@
                                 match_context_prop, yes, relation_possible)
 from cubicweb.utils import json_dumps
 from cubicweb.web import component, facet as facetbase
+from cubicweb.rqlrewrite import add_types_restriction
 
 def facets(req, rset, context, mainvar=None):
     """return the base rql and a list of widgets for facets applying to the
@@ -65,6 +66,17 @@
     if len(origqlst.children) != 1:
         req.debug('facette disabled on union request %s', origqlst)
         return None, ()
+
+    # Add type restriction to rql. This allow the get_type() method to return
+    # useful value on variable extracted from a select statement.
+    #
+    # This is done on origqlst to ensure all rql related objects are properly
+    # enriched when handled by a Facet:
+    #    - the rset.syntax_tree() during selection
+    #    - the select during selection
+    #    - the select during filtering
+
+    add_types_restriction(req.vreg.schema, origqlst.children[0])
     rqlst = origqlst.copy()
     select = rqlst.children[0]
     filtered_variable, baserql = facetbase.init_facets(rset, select, mainvar)
@@ -175,6 +187,20 @@
             hiddens['mainvar'] = mainvar
         filter_hiddens(w, baserql, wdgs, **hiddens)
         self.layout_widgets(w, self.sorted_widgets(wdgs))
+
+        # <Enter> is supposed to submit the form only if there is a single
+        # input:text field. However most browsers will submit the form
+        # on <Enter> anyway if there is an input:submit field.
+        #
+        # see: http://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2
+        #
+        # Firefox 7.0.1 does not submit form on <Enter> if there is more than a
+        # input:text field and not input:submit but does it if there is an
+        # input:submit.
+        #
+        # IE 6 or Firefox 2 behave the same way.
+        w(u'<input type="submit" class="hidden" />')
+        #
         w(u'</fieldset>\n')
         w(u'</form>\n')
 
--- a/web/views/tableview.py	Fri Oct 14 08:51:24 2011 +0200
+++ b/web/views/tableview.py	Fri Oct 14 09:21:45 2011 +0200
@@ -24,7 +24,7 @@
 
 from cubicweb import NoSelectableObject, tags
 from cubicweb.selectors import nonempty_rset
-from cubicweb.utils import make_uid, json_dumps
+from cubicweb.utils import make_uid, js_dumps, JSString
 from cubicweb.view import EntityView, AnyRsetView
 from cubicweb.uilib import toggle_action, limitsize, htmlescape
 from cubicweb.web import jsonize, component, facet
@@ -45,6 +45,11 @@
     table_widget_class = TableWidget
     table_column_class = TableColumn
 
+    tablesorter_settings = {
+        'textExtraction': JSString('cw.sortValueExtraction'),
+        'selectorHeaders: "thead tr:first th"' # only plug on the first row
+        }
+
     def form_filter(self, divid, displaycols, displayactions, displayfilter,
                     paginate, hidden=True):
         try:
@@ -84,6 +89,15 @@
                 displaycols = range(len(self.cw_rset.syntax_tree().children[0].selection))
         return displaycols
 
+    def _setup_tablesorter(self, divid):
+        req = self._cw
+        req.add_js('jquery.tablesorter.js')
+        req.add_onload('''$(document).ready(function() {
+    $("#%s table.listing").tablesorter(%s);
+});''' % (divid, js_dumps(self.tablesorter_settings)))
+        req.add_css(('cubicweb.tablesorter.css', 'cubicweb.tableview.css'))
+
+
     def call(self, title=None, subvid=None, displayfilter=None, headers=None,
              displaycols=None, displayactions=None, actions=(), divid=None,
              cellvids=None, cellattrs=None, mainindex=None,
@@ -98,12 +112,8 @@
         :param displayactions: if True, display action menu
         """
         req = self._cw
-        req.add_js('jquery.tablesorter.js')
-        req.add_onload('jQuery("table.listing").tablesorter({'
-                           'textExtraction: cubicwebSortValueExtraction,' # use our own function
-                           'selectorHeaders: "thead tr:first th"' # only plug on the first row
-                       '});')
-        req.add_css(('cubicweb.tablesorter.css', 'cubicweb.tableview.css'))
+        divid = divid or req.form.get('divid') or 'rs%s' % make_uid(id(self.cw_rset))
+        self._setup_tablesorter(divid)
         # compute label first  since the filter form may remove some necessary
         # information from the rql syntax tree
         if mainindex is None:
@@ -112,7 +122,6 @@
         hidden = True
         if not subvid and 'subvid' in req.form:
             subvid = req.form.pop('subvid')
-        divid = divid or req.form.get('divid') or 'rs%s' % make_uid(id(self.cw_rset))
         actions = list(actions)
         if mainindex is None:
             displayfilter, displayactions = False, False