Commit 3dc34697 authored by LE GAC Renaud's avatar LE GAC Renaud
Browse files

Refactor the report controllers and modules.

parent ac56e1fa
......@@ -6,221 +6,135 @@
- the content of the report is defined in the database
"""
import json
import re
from datetime import date, timedelta
from gluon.dal import smart_query
from gluon.storage import Storage
from plugin_dbui import INLINE_ALERT, Store
from reporting_tools import (Report,
StoreForListView,
StoreForMetric1DView,
StoreForMetric2DView,
StoreForYear2DView)
from plugin_dbui import INLINE_ALERT
from report_objects import List, Metric1D, Metric2D
from selector import MySelector
def graph():
"""Plot 2D metric as a Ext.char.series.Area
"""Plot 2D metric as a C{Ext.char.series.Area}
"""
graph = db.graphs[request.vars.id_graphs]
selector = virtdb.graph_selector
report = Report(selector, exclude_fields=('category',
'metric',
'period_end',
'period_start',
'year'))
ui_table = virtdb.graph_selector
selector = MySelector(ui_table, exclude_fields=('metric',))
# homomorphism between metric2D and graph
graph.field_vertical = graph.field_horizontal
graph.field_horizontal = graph.field_stacked
graph.metric = graph.metric_vertical
config = db.graphs[request.vars.id_graphs]
config.field_vertical = config.field_horizontal
config.field_horizontal = config.field_stacked
config.field_z = 'people.id'
config.metric = 'size'
# the metric might be superseded by the user
if report.metric:
graph.metric = report.metric
if selector.metric:
cpnfig.metric = config.metric
if not graph.metric:
if not config.metric:
return INLINE_ALERT % (T('Error'), T('Please select a metric!'))
# configure the Ext.data.Store
if graph.field_vertical == 'year':
store = StoreForYear2DView(graph, report)
else:
store = StoreForMetric2DView(graph, report)
# build report and extract the configurations for
# the Ext.data.Store and App.grid.Panel.
report = Metric2D(config, selector)
store = report.to_store()
# Extra the stack keys (value for metric.field_vertical)
stack_keys = store.values_h
stack_keys = [el for el in report.df.columns]
# delegate the rendering of the graph to the view
response.view = 'report/graph.html'
return dict(cfg_store=store.as_json(), stack_keys=stack_keys, view=graph)
return dict(cfg_store=json.dumps(store), stack_keys=stack_keys, view=config)
def list():
"""List renders by an Ext.grid.Panel.
"""List renders by an C{Ext.grid.Panel}.
The controller extracts the configuration of the list
and builds the configuration for the Ext.data.Array.
The latter is the core component embedded in the grid.
and builds the configuration for the C{Ext.data.Store}.
The latter is the core component embedded in the
C{App.grid.Panel}.
The list identifier is defined in the viewport
and send using the baseParams techniques.
"""
# alias
now = date.today()
list = db.lists[request.vars.id_lists]
report = Report(virtdb.list_selector, exclude_fields=('category',
'period_end',
'period_start',
'year'))
# apply the list condition
if list.conditions:
q_conditions = smart_query(db.history, list.conditions)
report.append_query(q_conditions)
# build the configuration for the Ext.data.Store
store = StoreForListView(list, report.select(db.history))
# encode the Ext.grid.column.dataIndex to match the Ext.data.Field name
s = list.columns.replace("\n", "").replace(" ", "")
s = re.sub("dataIndex:'(\w+)\.(\w+)'", "dataIndex:'\\1\\2'", s)
s = re.sub("dataIndex:'(\w+)\.(\w+)\.(\w+)'", "dataIndex:'\\1\\2\\3'", s)
list.columns = s
ui_table = virtdb.list_selector
selector = MySelector(ui_table)
# build report and extract the configurations for
# the Ext.data.Store and App.grid.Panel.
config = db.lists[request.vars.id_lists]
report = List(config, selector)
store = report.to_store()
grid = report.to_grid()
# delegate the grid rendering to the view
response.view = 'report/grid.html'
return dict(cfg_store=store.as_json(), view=list)
return dict(cfg_store=json.dumps(store), view=grid)
def metric1D():
"""One dimension metric renders by an Ext.grid.Panel.
"""One dimension metric renders by an C{App.grid.Panel}.
The controller extracts the configuration of the metric
and builds the configuration for the Ext.data.Array.
The latter is the core component embedded in the grid.
and builds the configuration for the C{Ext.data.Store}
as well as the configuration of the C{Ext.grid.column.Column}.
The metric identifier is defined in the viewport
and send using the baseParams techniques.
"""
metric = db.metrics[request.vars.id_metrics]
selector = virtdb.metric_selector_1D
report = Report(selector, exclude_fields=('category',
'metric',
'period_end',
'period_start',
'year'))
# apply filter condition from the metric
if metric.conditions:
q_conditions = smart_query(db.history, metric.conditions)
report.append_query(q_conditions)
# group condition for the vertical axis
groupfield = metric.field_vertical
tablename, fieldname = groupfield.split('.')
# configure the Ext.data.Store
store = StoreForMetric1DView(report.get_metric(groupfield))
# customize the standard view (Ext.grid.Panel)
view = Storage(columns=[], features=[], title=metric.title)
view.columns = [{'text': T(fieldname.title()),
'dataIndex': 'group',
'flex': 0.8},
{'text': '∑ person',
'dataIndex': 'count',
'align': 'right',
'flex': 0.5,
'summaryType': 'sum' },
{'text': '∑ fte',
'dataIndex': 'sum_fte',
'align': 'right',
'flex': 0.5,
'format': '0.00',
'summaryType': 'sum',
'xtype': 'numbercolumn'},
{'text': '<age>',
'dataIndex': 'avg_age',
'align': 'right',
'flex': 0.5,
'format': '0.00',
'xtype': 'numbercolumn'}]
view.features = [{'ftype': 'summary'}]
ui_table = virtdb.metric_selector_1D
selector = MySelector(ui_table, exclude_fields=('metric',))
# build report and extract the configuration
# for the Ext.data.Store and the App.grid.Panel
config = db.metrics[request.vars.id_metrics]
report = Metric1D(config, selector)
store = report.to_store()
grid = report.to_grid()
# JSON encoding
view.columns = json.dumps(view.columns)
view.features = json.dumps(view.features)
grid.columns = json.dumps(grid.columns)
grid.features = json.dumps(grid.features)
# delegate to standard view
response.view = 'report/grid.html'
return dict(cfg_store=store.as_json(), view=view)
return dict(cfg_store=json.dumps(store), view=grid)
def metric2D():
"""Two dimensions metric renders by an Ext.grid.Panel.
"""Two dimensions metric renders by an C{Ext.grid.Panel}.
The controller extracts the configuration of the metric
and builds the configuration for the Ext.data.Array.
The latter is the core component embedded in the grid.
and builds the configuration for the C{Ext.data.Store}.
The latter is the core component embedded in the
C{App.grid.Panel}.
The metric identifier is defined in the viewport
and send using the baseParams techniques.
"""
metric = db.metrics[request.vars.id_metrics]
selector = virtdb.metric_selector_2D
report = Report(selector, exclude_fields=('category',
'metric',
'period_end',
'period_start',
'year'))
ui_table = virtdb.metric_selector_2D
selector = MySelector(ui_table, exclude_fields=('metric',))
# the metric might be superseded by the user
if report.metric:
metric.metric = report.metric
if not metric.metric:
return INLINE_ALERT % (T('Error'), T('Please select a metric!'))
# build report and extract the configuration
# for the Ext.data.Store and the App.grid.Panel
config = db.metrics[request.vars.id_metrics]
config.field_z = 'people.id'
config.metric = 'size'
# configure the Ext.data.Store
if metric.field_vertical == 'year':
store = StoreForYear2DView(metric, report)
else:
store = StoreForMetric2DView(metric, report)
# customize the standard view (Ext.grid.Panel)
view = Storage(columns=[], features=[], title=metric.title)
view.columns.append({'text': report.metric,
'dataIndex': 'group',
'flex': 0.8})
for value_h in store.values_h:
view.columns.append({'text': str(value_h),
'dataIndex': str(value_h),
'align': 'right',
'flex': 0.5,
'format': '0.00',
'summaryType': 'sum',
'xtype': 'numbercolumn'})
view.features.append({'ftype': 'summary'})
report = Metric2D(config, selector)
store = report.to_store()
grid = report.to_grid()
# JSON encoding
view.columns = json.dumps(view.columns)
view.features = json.dumps(view.features)
grid.columns = json.dumps(grid.columns)
grid.features = json.dumps(grid.features)
# delegate the rendering to the standard view
response.view = 'report/grid.html'
return dict(cfg_store=store.as_json(), view=view)
return dict(cfg_store=json.dumps(store), view=grid)
......@@ -145,8 +145,8 @@
'Grid': 'Grid',
'Grid Columns': 'Grid Columns',
'Grid Features': 'Grid Features',
'Group': 'Group',
'Group by': 'Group by',
'Group': 'Groupe',
'Group by': 'Grouper par',
'Group Field': 'Group Field',
'Group ID': 'Group ID',
'Group uniquely assigned to user %(id)s': 'Group uniquely assigned to user %(id)s',
......@@ -296,6 +296,7 @@
'Start_Date': 'Start_Date',
'startswith': 'startswith',
'Store': 'Store',
'Summary': 'Résumé',
'Tablename': 'Tablename',
'Tables': 'Tables',
'Team': 'Équipe',
......
......@@ -39,13 +39,13 @@ formModifier.merge_fields('name',
formModifier.merge_fields('group_field',
'sorters',
title=T('Store'))
title=T('Group'))
formModifier.merge_fields('columns',
title=T('Grid Columns'))
title=T('Columns'))
formModifier.merge_fields('features',
title=T('Grid Features'))
title=T('Summary'))
formModifier.set_mapper(dbui.map_tabpanel)
formModifier.configure(width=450)
......
This diff is collapsed.
This diff is collapsed.
"""selector module
"""
from datetime import date, timedelta
from gluon import current
from gluon.dal import Field
from plugin_dbui import Selector
class SelectorActiveItems(Selector):
"""Selector to get records active during a given period of time.
The period of time are defined by the selector fields
C{_period_start} and C{_period_end} as well as C{_year}.
"""
def __init__(self,
table,
exclude_fields=('period_end',
'period_start',
'year')):
"""
@type table: gluon.dal.Table
@param table: the virtual table defining the selector.
@type exclude_fields: list of string
@param exclude_fields: name of the selector fields which are
excluded in the query.
"""
self.periode_end = None
self.period_start = None
self.year = None
Selector.__init__(self,
table,
exclude_fields=exclude_fields)
if self.year:
self.period_start = date(self.year, 1, 1)
self.period_end = date(self.year, 12, 31)
def query(self, table):
"""Build the database query for the database table
including inner join for foreign keys, selector constraints and
extra queries.
@type table: gluon.dal.Table
@param table: the database table in which records are selected.
@rtype: gluon.dal.Query
@return: The query selects active items during the given period of
time. It only works for table having the database fields
C{start_date} and C{end_date}.
"""
query = Selector.query(self, table)
if self.period_start and self.period_end:
q = table.start_date <= self.period_end
q = (q) & ((table.end_date == None) | (table.end_date >= self.period_start))
query = (query) & (q)
return query
class MySelector(SelectorActiveItems):
"""Main selector to build list, metric or graph according to
the user criteria:
- focus on history record
- Select record active during the selected period of time.
- Selection can be performed on category defined as a string.
- For each record computes virtual field age, coverage, duration,
fte and is_over.
"""
def __init__(self, table, exclude_fields=()):
"""
@type table: gluon.dal.Table
@param table: the virtual table defining the selector.
@type exclude_fields: tuple of string
@param exclude_fields: extend the list selector fields which are
excluded in the query. The field category, period_end, period_start
and year are systematically excluded.
"""
li = ['category', 'period_end', 'period_start', 'year']
li.extend(exclude_fields)
SelectorActiveItems.__init__(self, table, exclude_fields=li)
# add virtual fields
db = current.globalenv['db']
db.people.age = Field.Virtual('age', self._age, ftype='integer')
db.history.coverage = Field.Virtual('coverage', self._coverage, ftype='double')
db.history.duration = Field.Virtual('duration', self._duration, ftype='double')
db.history.fte = Field.Virtual('fte', self._fte, ftype='double')
db.history.is_over = Field.Virtual('is_over', self._is_over, ftype='boolean')
def _age(self, row):
"""Compute the age of a person of the person associated to
the history record.
@type row: gluon.dal.Row
@param row: row of the history table.
@rtype: int
"""
value = None
now = date.today()
if row.people.birth_date:
value = ((now - row.people.birth_date).days / 365)
return value
def _coverage(self, row):
"""Compute the fraction of time spend per a person on the
history record.
@type row: gluon.dal.Row
@param row: row of the history table.
@rtype: float
"""
value = 0.
if self.period_start and self.period_end:
start = max(self.period_start, row.history.start_date)
end = self.period_end
if row.history.end_date:
end = min(self.period_end, row.history.end_date)
x = (end - start).days
y = (self.period_end - self.period_start).days
value = float(x) / float(y)
return value
def _duration(self, row):
"""Compute the duration of the history record.
@type row: gluon.dal.Row
@param row: row of the history table.
@rtype: datetime.timedelta
"""
value = timedelta(seconds=0)
end = row.history.end_date
now = date.today()
start = row.history.start_date
if start and end and start <= now <= end:
value = now - start
elif start and end == None and start <= now:
value = now - start
elif start and end and end < now:
value = end - start
return value
def _fte(self, row):
"""Compute the full time equivalent that the person associated
to the history record spend on the record. It is a product of
the time coverage and the percentage of the person assigned to
the record.
@type row: gluon.dal.Row
@param row: row of the history table.
@rtype: float
"""
value = 0.
if row.history.percentage:
value = self._coverage(row) * (row.history.percentage / 100.)
return value
def _is_over(self, row):
"""Return true is the history record is over now.
@type row: gluon.dal.Row
@param row: row of the history table.
@rtype: bool
"""
end = row.history.end_date
now = date.today()
start = row.history.start_date
if end and start != end:
return end < now
return False
def query(self, table):
"""Supersede the base class method to handle category constraints.
@type table: gluon.dal.Table
@param table:
@note: Translate the selector category, defined as a string,
into a query.
@note: append the query on events for table containing the field
id_events.
"""
db = table._db
query = SelectorActiveItems.query(self, table)
# category constraint
if self.category:
q = db.people_categories.category == self.category
query = (query) & (q)
return query
def reset_extra_queries(self):
self._extra_queries = []
......@@ -39,9 +39,6 @@
}}
<script>
console.log(1, seriesKeys);
console.log(2, cfgStore);
// the axes
axes = [{
type: 'Numeric',
......@@ -52,7 +49,7 @@
position: 'left'
}, {
type: 'Category',
fields: ['group'],
fields: ['year'],
grid: true,
label: {
rotate: {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment