fix optimisation with super session that may lead to integrity loss
at some point I've decided to stop ensuring ?1 cardinality was respected
when adding a new relation using a super session, to avoid the cost of
the delete query. That was yet discussable because it introduced unexpected
difference between execute and unsafe_execute, which is imo not worth it.
Also, now that rql() in migration script default to unsafe_execute, we
definitly don't want that implicit behaviour change (which already cause
bug when for instance adding another default workflow for an entity type:
without that fix we end up with *two* default workflows while the schema
tells we can have only one.
IMO we should go to the direction that super session skip all security
check, but nothing else, unless explicitly asked.
"""basic plot views
:organization: Logilab
:copyright: 2007-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
import os
import time
from simplejson import dumps
from logilab.common import flatten
from logilab.common.date import datetime2ticks
from logilab.mtconverter import xml_escape
from cubicweb.utils import make_uid, UStringIO
from cubicweb.appobject import objectify_selector
from cubicweb.selectors import multi_columns_rset
from cubicweb.web.views import baseviews
@objectify_selector
def all_columns_are_numbers(cls, req, rset=None, *args, **kwargs):
"""accept result set with at least one line and two columns of result
all columns after second must be of numerical types"""
for etype in rset.description[0]:
if etype not in ('Int', 'Float'):
return 0
return 1
@objectify_selector
def second_column_is_number(cls, req, rset=None, *args, **kwargs):
etype = rset.description[0][1]
if etype not in ('Int', 'Float'):
return 0
return 1
@objectify_selector
def columns_are_date_then_numbers(cls, req, rset=None, *args, **kwargs):
etypes = rset.description[0]
if etypes[0] not in ('Date', 'Datetime'):
return 0
for etype in etypes[1:]:
if etype not in ('Int', 'Float'):
return 0
return 1
def filterout_nulls(abscissa, plot):
filtered = []
for x, y in zip(abscissa, plot):
if x is None or y is None:
continue
filtered.append( (x, y) )
return sorted(filtered)
class PlotWidget(object):
# XXX refactor with cubicweb.web.views.htmlwidgets.HtmlWidget
def _initialize_stream(self, w=None):
if w:
self.w = w
else:
self._stream = UStringIO()
self.w = self._stream.write
def render(self, *args, **kwargs):
w = kwargs.pop('w', None)
self._initialize_stream(w)
self._render(*args, **kwargs)
if w is None:
return self._stream.getvalue()
class FlotPlotWidget(PlotWidget):
"""PlotRenderer widget using Flot"""
onload = u"""
var fig = jQuery("#%(figid)s");
if (fig.attr('cubicweb:type') != 'prepared-plot') {
%(plotdefs)s
jQuery.plot(jQuery("#%(figid)s"), [%(plotdata)s],
{points: {show: true},
lines: {show: true},
grid: {hoverable: true},
xaxis: {mode: %(mode)s}});
jQuery("#%(figid)s").bind("plothover", onPlotHover);
fig.attr('cubicweb:type','prepared-plot');
}
"""
def __init__(self, labels, plots, timemode=False):
self.labels = labels
self.plots = plots # list of list of couples
self.timemode = timemode
def dump_plot(self, plot):
# XXX for now, the only way that we have to customize properly
# datetime labels on tooltips is to insert an additional column
# cf. function onPlotHover in cubicweb.flot.js
if self.timemode:
plot = [(datetime2ticks(x), y, datetime2ticks(x)) for x,y in plot]
return dumps(plot)
def _render(self, req, width=500, height=400):
if req.ie_browser():
req.add_js('excanvas.js')
req.add_js(('jquery.flot.js', 'cubicweb.flot.js'))
figid = u'figure%s' % req.varmaker.next()
plotdefs = []
plotdata = []
self.w(u'<div id="%s" style="width: %spx; height: %spx;"></div>' %
(figid, width, height))
for idx, (label, plot) in enumerate(zip(self.labels, self.plots)):
plotid = '%s_%s' % (figid, idx)
plotdefs.append('var %s = %s;' % (plotid, self.dump_plot(plot)))
# XXX ugly but required in order to not crash my demo
plotdata.append("{label: '%s', data: %s}" % (label.replace(u'&', u''), plotid))
req.html_headers.add_onload(self.onload %
{'plotdefs': '\n'.join(plotdefs),
'figid': figid,
'plotdata': ','.join(plotdata),
'mode': self.timemode and "'time'" or 'null'},
jsoncall=req.json_request)
class PlotView(baseviews.AnyRsetView):
__regid__ = 'plot'
title = _('generic plot')
__select__ = multi_columns_rset() & all_columns_are_numbers()
timemode = False
def call(self, width=500, height=400):
# prepare data
rqlst = self.cw_rset.syntax_tree()
# XXX try to make it work with unions
varnames = [var.name for var in rqlst.children[0].get_selected_variables()][1:]
abscissa = [row[0] for row in self.cw_rset]
plots = []
nbcols = len(self.cw_rset.rows[0])
for col in xrange(1, nbcols):
data = [row[col] for row in self.cw_rset]
plots.append(filterout_nulls(abscissa, data))
plotwidget = FlotPlotWidget(varnames, plots, timemode=self.timemode)
plotwidget.render(self._cw, width, height, w=self.w)
class TimeSeriePlotView(PlotView):
__select__ = multi_columns_rset() & columns_are_date_then_numbers()
timemode = True
try:
from GChartWrapper import Pie, Pie3D
except ImportError:
pass
else:
class PieChartWidget(PlotWidget):
def __init__(self, labels, values, pieclass=Pie, title=None):
self.labels = labels
self.values = values
self.pieclass = pieclass
self.title = title
def _render(self, width=None, height=None):
piechart = self.pieclass(self.values)
piechart.label(*self.labels)
if width is not None:
height = height or width
piechart.size(width, height)
if self.title:
piechart.title(self.title)
self.w(u'<img src="%s" />' % xml_escape(piechart.url))
class PieChartView(baseviews.AnyRsetView):
__regid__ = 'piechart'
pieclass = Pie
__select__ = multi_columns_rset() & second_column_is_number()
def _guess_vid(self, row):
etype = self.cw_rset.description[row][0]
if self._cw.vreg.schema.eschema(etype).final:
return 'final'
return 'textincontext'
def call(self, title=None, width=None, height=None):
labels = []
values = []
for rowidx, (_, value) in enumerate(self.cw_rset):
if value is not None:
vid = self._guess_vid(rowidx)
label = '%s: %s' % (self.view(vid, self.cw_rset, row=rowidx, col=0),
value)
labels.append(label.encode(self._cw.encoding))
values.append(value)
pie = PieChartWidget(labels, values, pieclass=self.pieclass,
title=title)
if width is not None:
height = height or width
pie.render(width, height, w=self.w)
class PieChart3DView(PieChartView):
__regid__ = 'piechart3D'
pieclass = Pie3D