[components] Backport and cleanup medicalexp components
authorVincent Michel <vincent.michel@logilab.fr>
Mon, 08 Jul 2013 16:33:56 +0200
changeset 269 3dfafb4debd5
parent 268 2e0c4fb017ba
child 270 312bc5dba074
[components] Backport and cleanup medicalexp components
data/cubes.brainomics.css
data/cubes.brainomics.js
data/logo.png
entities.py
uiprops.py
views/basetemplates.py
views/components.py
views/primary.py
views/secondary.py
views/urls.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/cubes.brainomics.css	Mon Jul 08 16:33:56 2013 +0200
@@ -0,0 +1,8 @@
+
+#toolbar{
+ padding-bottom: 10px;
+ }
+
+#header-right li{
+ list-style-type: none;
+ }
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/cubes.brainomics.js	Mon Jul 08 16:33:56 2013 +0200
@@ -0,0 +1,43 @@
+
+cw.cubes.brainomics = new Namespace('cw.cubes.brainomics');
+
+$.extend(cw.cubes.brainomics, {
+    getCurrentRql: function(){
+	// XXX This should be done in a more easier way...
+	var divid = 'pageContent';
+	var vidargs = '';
+	// Get facet rql
+	//jQuery(CubicWeb).trigger('facets-content-loading', [divid, '', '', vidargs]);
+	var $form = $('#' + divid + 'Form');
+	if ($form.length != 0){
+	    var zipped = facetFormContent($form);
+	    zipped[0].push('facetargs');
+	    zipped[1].push(vidargs);
+	    return zipped;}
+	else{return null;}
+    },
+
+    seeRelatedData: function(datatype, current_etype) {
+	var zipped = cw.cubes.brainomics.getCurrentRql();
+	if (zipped != null){
+	    var d = loadRemote(AJAX_BASE_URL, ajaxFuncArgs('filter_build_rql', null, zipped[0], zipped[1]));
+	    d.addCallback(function(result) {
+		    var rql = result[0];
+		    var dd = asyncRemoteExec('see_related_data', rql, datatype, current_etype);
+		    dd.addCallback(function(res) {window.location = res;});
+		    });
+	    };
+    },
+
+    changeDownloadUrls: function(){
+	/* Change the download urls for facet rql */
+	var zipped = cw.cubes.brainomics.getCurrentRql();
+	var d = loadRemote(AJAX_BASE_URL, ajaxFuncArgs('filter_build_rql', null, zipped[0], zipped[1]));
+	d.addCallback(function(result) {
+		    var rql = result[0];
+		    $.each($('.download-ctx'), function(index, value){
+			    this.href = BASE_URL+'?rql='+rql+'&vid='+this.id;
+			    console.log(BASE_URL+'?rql='+rql+'&vid='+this.id);});
+		    });
+	}
+});
\ No newline at end of file
Binary file data/logo.png has changed
--- a/entities.py	Wed Jul 17 11:25:47 2013 +0200
+++ b/entities.py	Mon Jul 08 16:33:56 2013 +0200
@@ -23,6 +23,8 @@
 from cubes.neuroimaging.entities import Scan
 from cubes.genomics.entities import GenomicMeasure
 
+MEASURES = ['GenericMeasure', 'Scan', 'GenericTestRun', 'QuestionnaireRun', 'GenomicMeasure']
+
 
 class BrainomicsScan(Scan):
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uiprops.py	Mon Jul 08 16:33:56 2013 +0200
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# copyright 2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2013 CEA (Saclay, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/
+
+# Bootswatch css
+STYLESHEETS += [data('bootstrap/css/bootstrap.css'),
+                data('cubes.brainomics.css'),]
+JAVASCRIPTS += [data('cubes.brainomics.js'),]
--- a/views/basetemplates.py	Wed Jul 17 11:25:47 2013 +0200
+++ b/views/basetemplates.py	Mon Jul 08 16:33:56 2013 +0200
@@ -4,11 +4,11 @@
 :copyright: 2013 LOGILAB S.A. (Paris, FRANCE), license is LGPL.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
+# XXX Backport from squareui
 
 __docformat__ = "restructuredtext en"
 
 from logilab.common.decorators import monkeypatch
-
 from logilab.mtconverter import xml_escape
 
 from cubicweb.utils import UStringIO
@@ -20,11 +20,9 @@
 basetemplates.TheMainTemplate.doctype = HTML5
 
 
-### XXX / TODOs
-###
-### <footer> is set by ContentFooter class. In orbui, it's set by
-###          the main template
-
+###############################################################################
+### GLOBAL CALL ###############################################################
+###############################################################################
 @monkeypatch(basetemplates.TheMainTemplate)
 def call(self, view):
     self.set_request_content_type()
@@ -53,6 +51,38 @@
     self.template_footer(view)
 
 
+###############################################################################
+### NAV COLUMN AND TOOLBAR ####################################################
+###############################################################################
+@monkeypatch(basetemplates.TheMainTemplate)
+def nav_toolbar(self, view):
+    boxes = list(self._cw.vreg['ctxcomponents'].poss_visible_objects(
+        self._cw, rset=self.cw_rset, view=view, context='nav-toolbar'))
+    if boxes:
+        for box in boxes:
+            box.render(w=self.w, view=view)
+
+@monkeypatch(basetemplates.TheMainTemplate)
+def nav_column(self, view, context):
+    boxes = list(self._cw.vreg['ctxcomponents'].poss_visible_objects(
+        self._cw, rset=self.cw_rset, view=view, context=context))
+    if boxes:
+        getlayout = self._cw.vreg['components'].select
+        self.w(u'<div id="aside-main-%s" class="span3">\n'
+               u'<div class="well">\n' %
+               context) # XXX Should arrange Facets soon
+        self.w(u'<div class="navboxes" id="navColumn%s">\n' % context.capitalize())
+        for box in boxes:
+            box.render(w=self.w, view=view)
+        self.w(u'</div>'
+               u'</div>'
+               u'</div>')
+    return len(boxes)
+
+
+###############################################################################
+### HEADER ####################################################################
+###############################################################################
 @monkeypatch(basetemplates.TheMainTemplate)
 def template_html_header(self, content_type, page_title,
                          additional_headers=()):
@@ -77,12 +107,17 @@
     if page_title:
         w(u'<title>%s</title>\n' % xml_escape(page_title))
 
-
 @monkeypatch(basetemplates.TheMainTemplate)
 def template_body_header(self, view):
     w = self.w
     w(u'<body>\n')
     self.wview('header', rset=self.cw_rset, view=view)
+    # Toolbar
+    w(u'<div id="toolbar" class="container-fluid">\n'
+      u'<div class="row-fluid offset3">\n')
+    self.nav_toolbar(view)
+    w(u'</div></div>\n')
+    # Page
     w(u'<div id="page" class="container-fluid">\n'
       u'<div class="row-fluid">\n')
     #w(u'<div class="span3">')
@@ -102,6 +137,32 @@
         msgcomp.render(w=self.w)
     self.content_header(view)
 
+@monkeypatch(basetemplates.HTMLPageHeader)
+def main_header(self, view):
+    """build the top menu with authentification info and the rql box"""
+    spans = {'headtext': 'span2',
+             'header-center': 'span8',
+             'header-right': 'span2',
+             }
+    w = self.w
+    w(u'<div id="header" class="navbar">'
+      u'<div class="navbar-inner">'
+      u'<div class="container">'
+      u'<div class="row-fluid">')
+    for colid, context in self.headers:
+        w(u'<div id="%s" class="%s">' % (colid, spans.get(colid, 'span2')))
+        components = self._cw.vreg['ctxcomponents'].poss_visible_objects(
+            self._cw, rset=self.cw_rset, view=view, context=context)
+        for comp in components:
+            comp.render(w=w)
+            w(u'&#160;')
+        w(u'</div>')
+    w(u'</div></div></div></div>\n')
+
+
+###############################################################################
+### FOOTER ####################################################################
+###############################################################################
 @monkeypatch(basetemplates.TheMainTemplate)
 def template_footer(self, view=None):
     self.w(u'<div class="row">')
@@ -115,45 +176,6 @@
            u'</div>')   # closes class="container-fluid"
     self.w(u'</body>')
 
-@monkeypatch(basetemplates.TheMainTemplate)
-def nav_column(self, view, context):
-    boxes = list(self._cw.vreg['ctxcomponents'].poss_visible_objects(
-        self._cw, rset=self.cw_rset, view=view, context=context))
-    if boxes:
-        getlayout = self._cw.vreg['components'].select
-        self.w(u'<div id="aside-main-%s" class="span3">\n'
-               u'<div class="well">\n' %
-               context) # XXX Should arrange Facets soon
-        self.w(u'<div class="navboxes" id="navColumn%s">\n' % context.capitalize())
-        for box in boxes:
-            box.render(w=self.w, view=view)
-        self.w(u'</div>'
-               u'</div>'
-               u'</div>')
-    return len(boxes)
-
-
-@monkeypatch(basetemplates.HTMLPageHeader)
-def main_header(self, view):
-    """build the top menu with authentification info and the rql box"""
-    spans = {'headtext': 'span2',
-             'header-center': 'span9',
-             'header-right': 'span1',
-             }
-    w = self.w
-    w(u'<div id="header" class="navbar">'
-      u'<div class="navbar-inner">'
-      u'<div class="container">')
-    for colid, context in self.headers:
-        w(u'<div id="%s" class="%s">' % (colid, spans.get(colid, 'span2')))
-        components = self._cw.vreg['ctxcomponents'].poss_visible_objects(
-            self._cw, rset=self.cw_rset, view=view, context=context)
-        for comp in components:
-            comp.render(w=w)
-            w(u'&#160;')
-        w(u'</div>')
-    w(u'</div></div></div>\n')
-
 
 @monkeypatch(basetemplates.HTMLPageFooter)
 def call(self, **kwargs):
--- a/views/components.py	Wed Jul 17 11:25:47 2013 +0200
+++ b/views/components.py	Mon Jul 08 16:33:56 2013 +0200
@@ -19,28 +19,170 @@
 from logilab.mtconverter import xml_escape
 
 from cubicweb import tags
-from cubicweb.predicates import (none_rset, one_line_rset, is_instance,
-                                 has_related_entities, match_view)
+from cubicweb.predicates import (none_rset, one_line_rset, is_instance, nonempty_rset,
+                                 has_related_entities, match_view, anonymous_user)
 from cubicweb.web import component
-
-from cubes.medicalexp.views.components import (MedicalExpRelatedDataAbstract,
-                                               MedicalExpAbstractDownloadBox)
+from cubicweb.web.views.boxes import SearchBox, EditBox
+from cubicweb.web.views.bookmark import BookmarksBox
 
 from cubes.brainomics.schema import ALL_MEASURES
 
 
+################################################################################
+### SEARCHBOX ##################################################################
+################################################################################
+class SearchBoxBrainomics(SearchBox):
+    """display a box with a simple search form
+    """
+    context = _('header-center')
+    formdef = (u'<div><form action="%(action)s" class="navbar-search pull-left">'
+               u'<input id="norql" type="text" accesskey="q" tabindex="%(tabindex1)s"'
+               u'       title="search text" value="%(value)s" name="rql"'
+               u'       class="search-query input-xlarge" placeholder="Search"/>'
+               u'       <input type="hidden" name="__fromsearchbox" '
+               u'              value="1" />'
+               u'       <input type="hidden" name="subvid" '
+               u'value="tsearch" />'
+               u'<!--input tabindex="%(tabindex2)s" type="submit" id="rqlboxsubmit"'
+               u'    class="rqlsubmit" value="" /-->'
+               u'</form></div>')
+
+    def render(self, w):
+        if self._cw.form.pop('__fromsearchbox', None):
+            rql = self._cw.form.get('rql', '')
+        else:
+            rql = ''
+        w(self.formdef % {'action': self._cw.build_url('view'),
+                          'tabindex1': self._cw.next_tabindex(),
+                          'value': xml_escape(rql),
+                          'tabindex2': self._cw.next_tabindex()})
+
+
+###############################################################################
+### NAVIGATION BOXES ##########################################################
+###############################################################################
+class BrainomicsNavToolbarDropdown(component.CtxComponent):
+    """ Abstractcomponent used to display related informations in navtoolbar
+    """
+    __abstract__ = True
+    __select__ = component.CtxComponent.__select__
+    context = 'nav-toolbar'
+
+    def display_title(self):
+        raise NotImplementedError
+
+    def related_infos(self, rset):
+        return []
+
+    def render(self, w, **kwargs):
+        infos = self.related_infos(self.cw_rset)
+        if not infos:
+            return
+        w(u'<div class="btn-group">')
+        w(u'<a class="btn dropdown-toggle btn-primary" data-toggle="dropdown" href="#">')
+        w(u'%s <span class="caret"></span></a>' % self.display_title())
+        w(u'<ul class="dropdown-menu">')
+        for ahref, title in infos:
+            w(u'<li><a href="%s">%s</a></li>' % (ahref, title))
+        w(u'</ul>')
+        w(u'</div>')
+
+
+class BrainomicsNabToolbarStudies(BrainomicsNavToolbarDropdown):
+    """ Component used to display related studies
+    """
+    __select__ = BrainomicsNavToolbarDropdown.__select__ & is_instance('Subject') & one_line_rset()
+    __regid__ = 'navtoolbar-studies-ctx'
+
+    def display_title(self):
+        return self._cw._('Related studies')
+
+    def related_infos(self, rset):
+        studies = set()
+        for entity in self.cw_rset.entities():
+            for study in entity.related_studies:
+                studies.add((study.absolute_url(), study.dc_title()))
+        return studies
+
+
+class BrainomicsNabToolbarGroups(BrainomicsNavToolbarDropdown):
+    """ Component used to display related groups
+    """
+    __select__ = BrainomicsNavToolbarDropdown.__select__ & is_instance('Subject') & one_line_rset()
+    __regid__ = 'navtoolbar-groups-ctx'
+
+    def display_title(self):
+        return self._cw._('Related groups')
+
+    def related_infos(self, rset):
+        groups = set()
+        for entity in self.cw_rset.entities():
+            for study in entity.related_groups:
+                groups.add((study.absolute_url(), study.dc_title()))
+        return groups
+
+
+class BrainomicsNabToolbarCenters(BrainomicsNavToolbarDropdown):
+    """ Component used to display related centers
+    """
+    __select__ = BrainomicsNavToolbarDropdown.__select__ & is_instance('Subject') & one_line_rset()
+    __regid__ = 'navtoolbar-centers-ctx'
+
+    def display_title(self):
+        return self._cw._('Related centers')
+
+    def related_infos(self, rset):
+        centers = set()
+        for entity in self.cw_rset.entities():
+            for study in entity.related_centers:
+                centers.add((study.absolute_url(), study.dc_title()))
+        return centers
+
+
 ###############################################################################
 ### DOWNLOAD BOXES ############################################################
 ###############################################################################
-class ScanZipFileBox(MedicalExpAbstractDownloadBox):
+class BrainomicsAbstractDownloadBox(component.CtxComponent):
+    __abstract__ = True
+    __select__ = component.CtxComponent.__select__ & nonempty_rset()
+    context = 'left'
+    always_available = False
+
+    def render(self, w, **kwargs):
+        demo = self._cw.vreg.config.get('demo-site')
+        if demo and not self.always_available:
+            self.render_demo(w)
+        else:
+            self.render_download(w)
+
+    def render_download(self, w):
+        """ Normal rendering """
+        url = self._cw.build_url(rql=self.cw_rset.printable_rql(), vid=self.download_vid)
+        _id = self.download_vid
+        w(u'<div><a class="btn btn-primary download-ctx" id="%s" href="%s"><i class="icon-download icon-white">'
+          '</i>%s</a></div></br>' % (_id, url, self._cw._(self.title)))
+        # Add on load the rql facet change
+        self._cw.add_onload("$(cw).bind('facets-content-loaded', cw.cubes.brainomics.changeDownloadUrls);")
+
+    def render_demo(self, w):
+        """ Render for demo """
+        w(u'<div><a href="#" class="btn btn-primary disabled" rel="popover" data-content="%s" '
+          'type="button" id="download_box_%s">'
+          '<i class="icon-download icon-white"></i>%s</a></div></br>'
+          % (self._cw._('This is a demo site. This feature is deactivated for now.'),
+             self.__regid__, self._cw._(self.title)))
+        self._cw.add_onload("$('#download_box_%s').popover()" % self.__regid__)
+
+
+class ScanZipFileBox(BrainomicsAbstractDownloadBox):
     __regid__ = 'scan_zipfile'
-    __select__ = MedicalExpAbstractDownloadBox.__select__  & is_instance('Scan', 'GenomicMeasure')
+    __select__ = BrainomicsAbstractDownloadBox.__select__  & is_instance('Scan', 'GenomicMeasure')
     title = _('Download Zip')
     download_vid = 'data-zip'
     order = 0
 
 
-class CsvFileBox(MedicalExpAbstractDownloadBox):
+class CsvFileBox(BrainomicsAbstractDownloadBox):
     __regid__ = 'measure_csvfile'
     __select__ = component.CtxComponent.__select__  & is_instance('Subject', 'Scan',
                                                                   'ScoreDef',
@@ -52,7 +194,7 @@
     download_vid = 'csvexport'
 
 
-class XcedeBox(MedicalExpAbstractDownloadBox):
+class XcedeBox(BrainomicsAbstractDownloadBox):
     __regid__ = 'xcede_file'
     __select__ = component.CtxComponent.__select__  & is_instance('Subject', 'Scan', 'GenomicMeasure',
                                                                   'QuestionnaireRun', 'Questionnaire')
@@ -62,21 +204,27 @@
     download_vid = 'xcede'
 
 
-class SeeSlicesBox(MedicalExpAbstractDownloadBox):
+class SeeSlicesBox(BrainomicsAbstractDownloadBox):
     __regid__ = 'see_slices'
-    __select__ = MedicalExpAbstractDownloadBox.__select__  & is_instance('Scan')
+    __select__ = BrainomicsAbstractDownloadBox.__select__  & is_instance('Scan')
     title = _('View images')
     download_vid = 'slices-view'
     order = 0
 
 
 ###############################################################################
-### RELATED DATA COMPONENTS ###################################################
+### REGISTRATION CALLBACK #####################################################
 ###############################################################################
-class BrainomicsRelatedMeasures(MedicalExpRelatedDataAbstract):
-    __regid__ = 'related.measures'
-    __select__ = MedicalExpRelatedDataAbstract.__select__  & is_instance('Study', 'Subject', *ALL_MEASURES)
-    order = 3
+BookmarksBox.__select__ = BookmarksBox.__select__ & ~anonymous_user()
+EditBox.__select__ = EditBox.__select__ & ~anonymous_user()
 
-    def render(self, w, **kwargs):
-        super(BrainomicsRelatedMeasures, self).render(w, measures=ALL_MEASURES)
+def registration_callback(vreg):
+    vreg.register_all(globals().values(), __name__, (SearchBoxBrainomics,))
+    vreg.register_and_replace(SearchBoxBrainomics, SearchBox)
+    # Unregister breadcrumbs
+    from cubicweb.web.views.ibreadcrumbs import (BreadCrumbEntityVComponent,
+                                                 BreadCrumbAnyRSetVComponent,
+                                                 BreadCrumbETypeVComponent)
+    vreg.unregister(BreadCrumbEntityVComponent)
+    vreg.unregister(BreadCrumbAnyRSetVComponent)
+    vreg.unregister(BreadCrumbETypeVComponent)
--- a/views/primary.py	Wed Jul 17 11:25:47 2013 +0200
+++ b/views/primary.py	Mon Jul 08 16:33:56 2013 +0200
@@ -17,29 +17,569 @@
 # with this program. If not, see <http://www.gnu.org/licenses/>.
 
 """cubicweb-opencat views/forms/actions/components for web ui"""
+from cubicweb.selectors import is_instance
+from cubicweb.web.views.primary import PrimaryView
 
-from cubes.questionnaire.views.primary import QuestionnaireRunPrimaryView
+from cubes.brainomics.entities import MEASURES
+
+
+###############################################################################
+### ABSTRACT PRIMARY VIEW #####################################################
+###############################################################################
+class BrainomicsPrimaryView(PrimaryView):
+    __select__ = PrimaryView.__select__
+    __abstract__ = True
+
+    def iterate_attributes(self, entity):
+        return []
+
+    def iterate_data(self, entity):
+        return []
+
+    def _build_data(self, data, rql, label):
+        rset = self._cw.execute(rql)
+        if len(rset):
+            url = self._cw.build_url(rql=rql)
+            ahref = u'<a href="%s">%s</a>' % (url, self._cw._('See all'))
+            data.append((label, (len(rset), ahref)))
+        return data
+
+    def display_main_col(self, entity):
+        pass
+
+    def display_additional_header(self, entity):
+        pass
+
+    def display_header(self, entity):
+        w = self.w
+        w(u'<div class="well">')
+        w(u'<div class="page-header">')
+        w(u'<h1>%s</h1>' % entity.dc_title())
+        w(u'</div>')
+        w(u'<dl class="dl-horizontal">')
+        for label, attribute in self.iterate_attributes(entity):
+            if attribute:
+                w(u'<dt>%s</dt><dd>%s</dd>' % (self._cw._(label), attribute))
+        w(u'</dl>')
+        # Additional header
+        self.display_additional_header(entity)
+        # Data
+        self.display_data_table(entity)
+        w(u'</div>')
+
+    def display_data_table(self, entity):
+        w = self.w
+        table_data = self.iterate_data(entity)
+        if not table_data:
+            return
+        w(u'<h3>%s</h3>' % self._cw._('Data overview'))
+        w(u'<table class="table table-striped table-bordered table-condensed">')
+        for label, data in table_data:
+            w(u'<tr>')
+            w(u'<th>%s</th>' % self._cw._(label))
+            for d in data:
+                w(u'<th>%s</th>' % d)
+            w(u'</tr>')
+        w(u'</table>')
+
+    def call(self, rset=None):
+        entity = self.cw_rset.get_entity(0,0)
+        w = self.w
+        # Header
+        self.display_header(entity)
+        # Main col
+        w(u'<div class="row-fluid">')
+        self.display_main_col(entity)
+        w(u'</div>')
+
+
+###############################################################################
+### SUBJECT ###################################################################
+###############################################################################
+class SubjectPrimaryView(BrainomicsPrimaryView):
+    __select__ = BrainomicsPrimaryView.__select__ & is_instance('Subject')
+
+    def iterate_attributes(self, entity):
+        return [(self._cw._('Age'), entity.age),
+                (self._cw._('Handedness'), entity.handedness),
+                (self._cw._('Gender'), entity.gender),]
+
+    def display_additional_header(self, entity):
+        scores = self._cw.execute('Any X WHERE S related_infos X, S eid %(e)s', {'e': entity.eid})
+        if scores:
+            self.w(u'<h3>%s</h3>' % self._cw._('Scores'))
+            self.wview('list', scores)
+
+    def iterate_data(self, entity):
+        data = []
+        for measure in MEASURES:
+            rql = 'Any X WHERE X is %s, X concerns S, S eid %s' % (measure, entity.eid)
+            data = self._build_data(data, rql, self._cw._(measure))
+        return data
+
+    def display_main_col(self, entity):
+        self.w(u'<div class="span6">')
+        # Detailled assessments
+        rset = self._cw.execute('Any X WHERE S concerned_by X, S eid %(e)s', {'e': entity.eid})
+        if rset:
+            self.w(u'<h3>%s</h3>' % self._cw._('Detailed Assessments'))
+            self.wview('list', rset=rset)
+        self.w(u'</div>')
+        self.w(u'<div class="span5">')
+        # Related external resources
+        rset = self._cw.execute('Any E WHERE X external_resources E, S concerned_by X, S eid %(e)s, '
+                                'NOT EXISTS(X generates M)', {'e': entity.eid})
+        if rset:
+            self.w(u'<h3>%s</h3>' % self._cw._('External Resources'))
+            self.wview('list', rset=rset)
+        self.w(u'</div>')
+
+
+############################################################################
+### STUDY ##################################################################
+############################################################################
+class StudyPrimaryView(BrainomicsPrimaryView):
+    __select__ = BrainomicsPrimaryView.__select__ & is_instance('Study')
+
+    def iterate_attributes(self, entity):
+        return [(self._cw._('Name'), entity.name),
+                (self._cw._('Description'), entity.description),
+                (self._cw._('Keywords'), entity.keywords),
+                (self._cw._('Themes'), ', '.join([t.dc_title() for t in entity.themes]))]
+
+    def iterate_data(self, entity):
+        data = []
+        # Subjects
+        rql = 'Any X WHERE X related_studies S, S eid %s' % entity.eid
+        data = self._build_data(data, rql, self._cw._('Subjects'))
+        # Measures
+        rset = self._cw.execute('Any M, COUNT(X) GROUPBY S, M WHERE X related_study S, '
+                                'S eid %s, X is E, E name M' % entity.eid)
+        for label, count in rset:
+            rql='Any X WHERE X is %s, X related_study S, S eid %s' % (label, entity.eid)
+            data = self._build_data(data, rql, self._cw._(label))
+        return data
+
+
+###############################################################################
+### INVESTIGATOR ##############################################################
+###############################################################################
+class InvestigatorPrimaryView(BrainomicsPrimaryView):
+    __select__ = BrainomicsPrimaryView.__select__ & is_instance('Investigator')
+
+    def iterate_attributes(self, entity):
+        return [(self._cw._('Firstname'), entity.firstname),
+                (self._cw._('Lastname'), entity.lastname),
+                (self._cw._('Title'), entity.title),
+                (self._cw._('Institution'), entity.institution),
+                (self._cw._('Department'), entity.department),]
+
+    def display_main_col(self, entity):
+        # Detailled assessments
+        # XXX Direct link to the measures/runs ?
+        self.w(u'<h3>%s</h3>' % self._cw._('Assessments conducted by this investigator'))
+        rset = self._cw.execute('Any A WHERE A conducted_by X, X eid %(e)s', {'e': entity.eid})
+        if rset:
+            self.wview('list', rset)
 
 
-class BrainomicsQuestionnaireRunPrimaryView(QuestionnaireRunPrimaryView):
+############################################################################
+### CENTER #################################################################
+############################################################################
+class CenterPrimaryView(BrainomicsPrimaryView):
+    __select__ = BrainomicsPrimaryView.__select__ & is_instance('Center')
+
+    def iterate_attributes(self, entity):
+        return [(self._cw._('Identifier'), entity.identifier),
+                (self._cw._('Department'), entity.department),
+                (self._cw._('City'), entity.city),
+                (self._cw._('Country'), entity.country),]
+
+    def iterate_data(self, entity):
+        data = []
+        # Subjects
+        rql = 'DISTINCT Any S WHERE S is Subject, X holds A, S concerned_by A, X eid %s' % entity.eid
+        data = self._build_data(data, rql, self._cw._('Subjects'))
+        # Assessments
+        rql = 'DISTINCT Any A WHERE X holds A, X eid %s' % entity.eid
+        data = self._build_data(data, rql, self._cw._('Assessments'))
+        return data
+
+    def display_main_col(self, entity):
+        # Devices
+        rset = self._cw.execute('Any S WHERE S is Device, '
+                                'S hosted_by X, X eid %(e)s', {'e': entity.eid})
+        if rset:
+            self.w(u'<h3>Devices</h3>')
+            self.wview('list', rset=rset)
+
+
+############################################################################
+### DEVICE #################################################################
+############################################################################
+class DevicePrimaryView(BrainomicsPrimaryView):
+    __select__ = BrainomicsPrimaryView.__select__ & is_instance('Device')
+
+    def iterate_attributes(self, entity):
+        center = entity.hosted_by[0]
+        center = u'<a href="%s">%s</a>' % (center.absolute_url(), center.dc_title())
+        return [(self._cw._('Name'), entity.name),
+                (self._cw._('Manufacturer'), entity.manufacturer),
+                (self._cw._('Model'), entity.model),
+                (self._cw._('Hosted by'), center),]
+
+    def iterate_data(self, entity):
+        data = []
+        rql = 'DISTINCT Any S WHERE S uses_device X, X eid %s' % entity.eid
+        data = self._build_data(data, rql, self._cw._('Generated measures'))
+        return data
+
+
+############################################################################
+### ASSESSMENT #############################################################
+############################################################################
+class AssessmentPrimaryView(BrainomicsPrimaryView):
+    __select__ = BrainomicsPrimaryView.__select__ & is_instance('Assessment')
+
+    def iterate_attributes(self, entity):
+        subject = entity.reverse_concerned_by[0]
+        subject = u'<a href="%s">%s</a>' % (subject.absolute_url(), subject.dc_title())
+        return [(self._cw._('Identifier'), entity.identifier),
+                (self._cw._('Protocol'), entity.protocol),
+                (self._cw._('Subject'), subject),
+                (self._cw._('Study'), entity.related_study[0].view('outofcontext')),
+                (self._cw._('Center'), entity.reverse_holds[0].view('outofcontext')),
+                ]
+
+    def iterate_data(self, entity):
+        data = []
+        # Generated
+        rset = self._cw.execute('Any M, COUNT(S) GROUPBY M WHERE X generates S, X eid %s, '
+                                'S is E, E name M' % entity.eid)
+        for m, count in rset:
+            rql = 'Any S WHERE X generates S, S is %s, X eid %s' % (m, entity.eid)
+            data = self._build_data(data, rql, self._cw._('Generated %(s)s' % {'s': m}))
+        # Used
+        rset = self._cw.execute('Any M, COUNT(S) GROUPBY M WHERE X uses S, X eid %s, '
+                                'S is E, E name M' % entity.eid)
+        for m, count in rset:
+            rql = 'Any S WHERE X uses S, S is %s, X eid %s' % (m, entity.eid)
+            data = self._build_data(data, rql, self._cw._('Used %(s)s' % {'s': m}))
+        return data
+
+
+###############################################################################
+### SCOREDEF ##################################################################
+###############################################################################
+class ScoreDefPrimaryView(BrainomicsPrimaryView):
+    __select__ = BrainomicsPrimaryView.__select__ & is_instance('ScoreDef')
+
+    def iterate_attributes(self, entity):
+        return [(self._cw._('Name'), entity.name),
+                (self._cw._('Category'), entity.category),
+                (self._cw._('Type'), entity.type),
+                (self._cw._('Unit'), entity.unit),
+                (self._cw._('Possible values'), entity.possible_values)
+                ]
 
-    def call(self, rset=None):
-        super(BrainomicsQuestionnaireRunPrimaryView, self).call(rset=rset)
-        rset = rset or self.cw_rset
-        entity = rset.get_entity(0, 0)
-        # Add measures
+    def display_main_col(self, entity):
+        # Scores
+        w = self.w
+        w(u'<table class="table table-striped table-bordered table-condensed">')
+        w(u'<tr><th>%s</th><th>%s</th><th>%s</th></tr>'
+          % (self._cw._('Subject'), self._cw._('value'), self._cw._('datetime')))
+        rset = self._cw.execute('Any S,V,T,DA WHERE S is ScoreValue, S value V, S text T, '
+                                'S datetime DA, S definition D, D eid %(e)s', {'e': entity.eid})
+        for ind in range(len(rset)):
+            score = rset.get_entity(ind, 0)
+            subj = score.subject
+            if subj:
+                subject = u'<a href="%s">%s</a>' % (subj.absolute_url(), subj.identifier)
+            else:
+                subject = u'<unknown - error>'
+            w(u'<tr><th>%s</th><th>%s</th><th>%s</th></tr>'
+              % (subject, score.complete_value, score.datetime or '-'))
+        w(u'</table>')
+
+
+###############################################################################
+### GENERIC TEST RUN ##########################################################
+###############################################################################
+class GenericTestRunPrimaryView(BrainomicsPrimaryView):
+    __select__ = BrainomicsPrimaryView.__select__ & is_instance('GenericTestRun')
+
+    def iterate_attributes(self, entity):
+        test = entity.instance_of[0]
+        test = u'<a href="%s">%s</a>' % (test.absolute_url(), test.dc_title())
+        subject = entity.concerns[0]
+        subject = u'<a href="%s">%s</a>' % (subject.absolute_url(), subject.dc_title())
+        return [(self._cw._('Date'), entity.datetime),
+                (self._cw._('Instance of'), test),
+                (self._cw._('Subject'), subject),]
+
+    def display_main_col(self, entity):
+        w = self.w
+        # Scores
+        self.w(u'<div class="span9">')
+        rset = self._cw.execute('Any SD, S WHERE QR is GenericTestRun, QR eid %(e)s, '
+                                'S measure QR, S definition SD, S value V, S text T',
+                                {'e': entity.eid})
+        w(u'<table class="table table-striped table-bordered table-condensed">')
+        w(u'<tr>')
+        for label in ('score', 'text/value'):
+                        w(u'<th>%s</th>' % self._cw._(label))
+        w(u'</tr>')
+        for ind, scoredef in enumerate(rset.entities()):
+            w(u'<tr>')
+            w(u'<th><a href="%s">%s</a></th>' % (scoredef.absolute_url(), scoredef.dc_title()))
+            w(u'<th>%s</th>' % (rset.get_entity(ind, 1).complete_value))
+            w(u'</tr>')
+        w(u'</table>')
+        self.w(u'</div>')
+        # Related external resources
+        self.w(u'<div class="span3">')
+        rset = self._cw.execute('(Any E WHERE X external_resources E, X eid %(e)s) '
+                                'UNION (Any E WHERE A generates X, A external_resources E, X eid %(e)s)',
+                                {'e': entity.eid})
+        if rset:
+            w(u'<h3>%s</h3>' % self._cw._('External Resources'))
+            self.wview('list', rset=rset)
+        self.w(u'</div>')
+
+
+###############################################################################
+### GENERIC TEST ##############################################################
+###############################################################################
+class GenericTestPrimaryView(BrainomicsPrimaryView):
+    __select__ = BrainomicsPrimaryView.__select__ & is_instance('GenericTest')
+
+    def iterate_attributes(self, entity):
+        return [(self._cw._('Identifier'), entity.identifier),
+                (self._cw._('Name'), entity.name),
+                (self._cw._('Type'), entity.type),
+                (self._cw._('Version'), entity.version),
+                (self._cw._('Language'), entity.language),
+                (self._cw._('Note'), entity.note),]
+
+    def iterate_data(self, entity):
+        data = []
+        rql = 'Any S WHERE S instance_of X, X eid %s' % entity.eid
+        data = self._build_data(data, rql, self._cw._('Test runs'))
+        return data
+
+
+###############################################################################
+### EXTERNAL RESOURCE #########################################################
+###############################################################################
+class ExternalResourcePrimaryView(BrainomicsPrimaryView):
+    __select__ = BrainomicsPrimaryView.__select__ & is_instance('ExternalResource')
+
+    def iterate_attributes(self, entity):
+        study = entity.related_study
+        study = u'<a href="%s">%s</a>' % (study[0].absolute_url(), study[0].dc_title()) if study else u''
+        return [(self._cw._('Name'), entity.name),
+                (self._cw._('Filepath'), entity.filepath),
+                (self._cw._('Study'), study),
+                ]
+
+
+###############################################################################
+### QUESTIONNAIRERUN ##########################################################
+###############################################################################
+class QuestionnaireRunPrimaryView(BrainomicsPrimaryView):
+    __select__ = BrainomicsPrimaryView.__select__ & is_instance('QuestionnaireRun')
+
+    def iterate_attributes(self, entity):
+        questionnaire = entity.instance_of[0]
+        questionnaire = u'<a href="%s">%s</a>' % (questionnaire.absolute_url(), questionnaire.dc_title())
+        subject = entity.concerns[0]
+        subject = u'<a href="%s">%s</a>' % (subject.absolute_url(), subject.dc_title())
+        assessment = entity.reverse_generates[0]
+        assessment = u'<a href="%s">%s</a>' % (assessment.absolute_url(), assessment.dc_title())
+        return [(self._cw._('Date'), entity.datetime),
+                (self._cw._('Instance of'), questionnaire),
+                (self._cw._('Subject'), subject),
+                (self._cw._('Assessment'), assessment),
+                ]
+
+
+    def display_additional_header(self, entity):
+        w = self.w
+        # Scores and External resources
         rset = self._cw.execute('Any X WHERE X is ScoreValue, X measure Q, Q eid %(q)s',
                                 {'q': entity.eid})
         if rset:
-            self.w(u'<h3>%s</h3>' % self._cw._('Additional scores'))
+            w(u'<h3>%s</h3>' % self._cw._('Additional scores'))
             self.wview('list', rset=rset)
-        # Add external resources
         rset = self._cw.execute('Any X WHERE X is ExternalResource, Q external_resources X, Q eid %(q)s',
                                 {'q': entity.eid})
         if rset:
-            self.w(u'<h3>%s</h3>' % self._cw._('Additional resources'))
+            w(u'<h3>%s</h3>' % self._cw._('Additional resources'))
             self.wview('list', rset=rset)
 
+    def display_main_col(self, entity):
+        w = self.w
+        # Answers
+        w(u'<h3>%s</h3>' % self._cw._('Answers'))
+        rset = self._cw.execute('Any Q, A, AV, AD ORDERBY QP '
+                                'WHERE QR is QuestionnaireRun, QR eid %(e)s, '
+                                'A questionnaire_run QR, A question Q, '
+                                'A value AV, A datetime AD, '
+                                'Q identifier QI, Q position QP, Q text QT, '
+                                'Q type QTY, Q possible_answers QPA',
+                                {'e': entity.eid})
+        w(u'<table class="table table-striped table-bordered table-condensed">')
+        w(u'<tr>')
+        for label in ('question', 'text', 'value', 'datetime'):
+            w(u'<th>%s</th>' % self._cw._(label))
+        w(u'</tr>')
+        for ind, question in enumerate(rset.entities()):
+            w(u'<tr>')
+            url = self._cw.build_url(rql='Any A, I, AV, AD WHERE A is Answer, A question Q, Q eid %s, '
+                                     'A value AV, A datetime AD, A questionnaire_run AQ, '
+                                     'AQ concerns S, S identifier I' % question.eid,
+                                     vid='table',__force_display=True)
+            w(u'<th><a href="%s">%s</a></th>' % (url, question.identifier))
+            w(u'<th>%s</th>' % question.text)
+            answer = rset.get_entity(ind, 1)
+            w(u'<th>%s</th>' % answer.computed_value)
+            w(u'<th>%s</th>' % (answer.datetime or self._cw._(u'unknown')))
+            w(u'</tr>')
+        w(u'</table>')
 
-def registration_callback(vreg):
-    vreg.register_and_replace(BrainomicsQuestionnaireRunPrimaryView, QuestionnaireRunPrimaryView)
+
+###############################################################################
+### QUESTIONNAIRE #############################################################
+###############################################################################
+class QuestionnairePrimaryView(BrainomicsPrimaryView):
+    __select__ = BrainomicsPrimaryView.__select__ & is_instance('Questionnaire')
+
+    def iterate_attributes(self, entity):
+        return [(self._cw._('Name'), entity.name),
+                (self._cw._('Type'), entity.type),
+                (self._cw._('Version'), entity.version),
+                (self._cw._('Language'), entity.language),
+                (self._cw._('Note'), entity.note),
+                ]
+
+    def iterate_data(self, entity):
+        data = []
+        rql = 'Any S WHERE S is QuestionnaireRun, S instance_of X, X eid %s' % entity.eid
+        data = self._build_data(data, rql, self._cw._('Related runs'))
+        return data
+
+    def display_main_col(self, entity):
+        # Questions
+        w = self.w
+        rset = self._cw.execute('Any Q, QI, QP, QT, QTY, QPA ORDERBY QP '
+                                'WHERE QR is Questionnaire, QR eid %(e)s, '
+                                'Q questionnaire QR, '
+                                'Q identifier QI, Q position QP, Q text QT, '
+                                'Q type QTY, Q possible_answers QPA',
+                                {'e': entity.eid})
+        w(u'<table class="table table-striped table-bordered table-condensed">')
+        w(u'<tr>')
+        for label in ('question', 'text', 'type', 'possible_answers', 'Answers'):
+            w(u'<th>%s</th>' % self._cw._(label))
+        w(u'</tr>')
+        for question in rset.entities():
+            w(u'<tr>')
+            possible_answers = question.displayable_possible_answers
+            for value in (question.identifier, question.text, question.type, possible_answers):
+                w(u'<th>%s</th>' % value)
+            w(u'<th><a href="%s">%s</a></th>' % (question.absolute_url(), self._cw._('See detailled question')))
+            w(u'</tr>')
+        w(u'</table>')
+
+
+###############################################################################
+### QUESTION ##################################################################
+###############################################################################
+class QuestionPrimaryView(BrainomicsPrimaryView):
+    __select__ = BrainomicsPrimaryView.__select__ & is_instance('Question')
+
+    def iterate_attributes(self, entity):
+        questionnaire = entity.questionnaire[0]
+        questionnaire = u'<a href="%s">%s</a>' % (questionnaire.absolute_url(), questionnaire.dc_title())
+        return [(self._cw._('Questionnaire'), questionnaire),
+                (self._cw._('Position'), entity.position),
+                (self._cw._('Text'), entity.text),
+                (self._cw._('Type'), entity.type),
+                (self._cw._('Possible answers'), entity.possible_answers),
+                ]
+
+    def display_main_col(self, entity):
+        # Answers
+        w = self.w
+        rset = self._cw.execute('Any A, AV, AD, AQ, I WHERE A is Answer, A question Q, Q eid %(e)s, '
+                                'A value AV, A datetime AD, '
+                                'A questionnaire_run AQ, AQ concerns S, S identifier I',
+                                {'e': entity.eid})
+        w(u'<table class="table table-striped table-bordered table-condensed">')
+        w(u'<tr>')
+        for label in ('answer',  'value', 'datetime'):
+            w(u'<th>%s</th>' % self._cw._(label))
+        w(u'</tr>')
+        for ind, answer in enumerate(rset.entities()):
+            # XXX WARM CACHE ?
+            questionnaire_run = rset.get_entity(ind, 3)
+            w(u'<tr>')
+            w(u'<th><a href="%s">%s</a></th>' % (answer.absolute_url(), answer.dc_title()))
+            w(u'<th>%s</th>' % answer.computed_value)
+            w(u'<th>%s</th>' % (answer.datetime or self._cw._(u'unknown')))
+            w(u'</tr>')
+        w(u'</table>')
+
+
+###############################################################################
+### SCAN ######################################################################
+###############################################################################
+class ScanPrimaryView(BrainomicsPrimaryView):
+    __select__ = BrainomicsPrimaryView.__select__ & is_instance('Scan')
+
+    def iterate_attributes(self, entity):
+        subject = entity.concerns[0]
+        subject = u'<a href="%s">%s</a>' % (subject.absolute_url(), subject.dc_title())
+        device = entity.uses_device[0]
+        device = u'<a href="%s">%s</a>' % (device.absolute_url(), device.dc_title())
+        assessment = entity.reverse_generates[0]
+        assessment = u'<a href="%s">%s</a>' % (assessment.absolute_url(), assessment.dc_title())
+        return [(self._cw._('Identifier'), entity.identifier),
+                (self._cw._('Label'), entity.label),
+                (self._cw._('Type'), entity.type),
+                (self._cw._('Format'), entity.format),
+                (self._cw._('Position in acquisition'), entity.position_acquisition),
+                (self._cw._('Subject'), subject),
+                (self._cw._('Device'), device),
+                (self._cw._('Assessment'), assessment),
+                ]
+
+    def display_header(self, entity):
+        w = self.w
+        w(u'<div class="well">')
+        w(u'<div class="page-header">')
+        w(u'<h1>%s</h1>' % entity.dc_title())
+        w(u'</div>')
+        w(u'<dl class="dl-horizontal">')
+        for label, attribute in self.iterate_attributes(entity):
+            if attribute:
+                w(u'<dt>%s</dt><dd>%s</dd>' % (self._cw._(label), attribute))
+        # Add data info
+        data = entity.has_data
+        if data:
+            data[0].view('scan-data-view', w=w)
+        w(u'</dl>')
+        # Additional header
+        self.display_additional_header(entity)
+        # Data
+        self.display_data_table(entity)
+        w(u'</div>')
+
+    def display_main_col(self, entity):
+        # Related external resources
+        rset = self._cw.execute('(Any E WHERE X external_resources E, X eid %(e)s) '
+                                'UNION (Any E WHERE A generates X, A external_resources E, X eid %(e)s)',
+                                {'e': entity.eid})
+        if rset:
+            self.w(u'<h3>%s</h3>' % self._cw._('External Resources'))
+            self.wview('list', rset=rset)
--- a/views/secondary.py	Wed Jul 17 11:25:47 2013 +0200
+++ b/views/secondary.py	Mon Jul 08 16:33:56 2013 +0200
@@ -17,24 +17,14 @@
 # with this program. If not, see <http://www.gnu.org/licenses/>.
 
 
-from cubicweb.web.views.baseviews import SameETypeListView
 from cubicweb.web.views.baseviews import ListView
 
-# Redefine the view in order to add the correct css class for Bootstrap
-class OrbuiSameETypeLisView(SameETypeListView):
-
-    def call(self, klass=None, title=None, subvid=None, listid=None, **kwargs):
-        self.w(u"<div class='span9'>")
-        super(OrbuiSameETypeLisView, self).call(**kwargs)
-        self.w(u"</div>")
-
-
 class OrbuiListView(ListView):
 
     def call(self, klass=None, title=None, subvid=None, listid=None, **kwargs):
-        super(OrbuiListView, self).call(klass="unstyled stripped", title=title, subvid=subvid, listid=listid, **kwargs)
+        super(OrbuiListView, self).call(klass="unstyled stripped",
+                                        title=title, subvid=subvid, listid=listid, **kwargs)
 
 
 def registration_callback(vreg):
-    vreg.register_and_replace(OrbuiSameETypeLisView, SameETypeListView)
     vreg.register_and_replace(OrbuiListView, ListView)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/urls.py	Mon Jul 08 16:33:56 2013 +0200
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# copyright 2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2013 CEA (Saclay, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from cubicweb.web.views.urlrewrite import URLRewriter, SimpleReqRewriter, rgx
+
+
+class BrainomicsReqRewriter(SimpleReqRewriter):
+    ignore_baseclass_rules = True
+    rules = [
+        ### Identifier
+        (rgx('/id/(.+?)'), dict(rql=r'Any X WHERE X identifier "\1"')),
+        ]