Commit 128c9b46 authored by LE GAC Renaud's avatar LE GAC Renaud
Browse files

Remove obsolete modules.

parent f64805a9
# -*- coding: utf-8 -*-
"""plugin_event controllers
"""
import urllib
import json
import re
from datetime import datetime
from plugin_dbui import get_id, INLINE_ALERT
from plugin_event import (do_title,
Graph,
List,
Metric1D,
Metric2D,
ReportException)
from plugin_event import EvtSelector, SelectorActiveItemsException
MSG = T(" - %s entry(ies) modified in the history table. <br>"
" - DO NOT FORGET TO MODIFIED THE REPORT CONFIGURATION.")
MSG_CVT = T(" - Conversion error: %s.")
def graph_mpl():
"""Plot list or metric using the matplotlib library
"""
# build the graph
try:
graph = Graph(db.graphs, request.vars.id_graphs)
except (IndexError, TypeError, ValueError) as e:
return INLINE_ALERT % ("GRAPH Error", e)
# build the report title
title = do_title(graph)
# extract the image
# encode special character to be used in the image URI
extension = request.extension
if extension == "html":
data = graph.to_svg()
data = urllib.quote(data)
elif extension == "pdf":
data = graph.to_pdf()
elif extension == "png":
data = graph.to_png()
# delegate the rendering to the view
return dict(data=data, title=title)
def grid():
"""Render the report through the C{Dbui.grid.Panel}.
The report can be a list, a metrics 1d or 2d.
The controller extracts the configuration of the report form the database.
It builds the DataFrame, extracts the the configuration
for the C{Ext.data.Store} as well as the C{Dbui.grid.Panel}.
The former contains the data displayed in the grid.
The report identifier is defined in the viewport
and send using the baseParams techniques. Possible identifiers
are C{id_lists], C{id_metrics1d} and C{id_metrics2d}.
The identifier is used to instantiate the proper class.
"""
# build the report
try:
if "id_lists" in request.vars:
report = List(db.lists, request.vars.id_lists)
elif "id_metrics1d" in request.vars:
report = Metric1D(db.metrics1d, request.vars.id_metrics1d)
elif "id_metrics2d" in request.vars:
report = Metric2D(db.metrics2d, request.vars.id_metrics2d)
else:
return INLINE_ALERT % ("Error", "Report id is not defined.")
except (IndexError, ReportException, TypeError, ValueError) as e:
return INLINE_ALERT % ("REPORT Error...", e)
# extract the configurations for the Ext.data.Store and Dbui.grid.Panel.
store = report.to_store()
grid = report.to_grid()
# build the report title
title = do_title(report)
# delegate the grid rendering to the view
return dict(cfg_store=store, grid=grid, title=title)
def userModelConsistency():
""" A user data block is associated to each event.
It is configurable by the user and defined in the events table.
The definition of the user data block (model) and the content
of the history.data field have to be kept in synchronization.
This job is done by this controller.
"""
# decode the request
changes = json.loads(request.vars.changes)
event = request.vars.fieldEvent
model = json.loads(request.vars.fieldData)
# build a map of the modified keys.
# each operation is a list containing (action, old key, new key).
# the action is equal to: add, destroy, update
#
# NOTE: add and delete action are handle via dictionary copy.
#
#
update_keys = {}
for action, oldkey, newkey in changes:
if action != "update":
continue
# simplify the case where key1 > key2 > key3 to key1 > key3
# remove the case where key1 > key2 > ... > key1
if update_keys:
ok = False
for k, v in update_keys.iteritems():
if oldkey == v:
update_keys[k] = newkey
ok = True
if k == newkey:
del update_keys[k]
break
if ok:
continue
# basic case
update_keys[oldkey] = newkey
# get the event id
id_event = get_id(db.events, event=event)
if id_event == None:
return
# set-up the default values dealing with data conversion
for key in model:
value = model[key]["value"]
type = model[key]["type"]
if value:
try:
if type == "boolean":
value = value.lower() in ("true", "vrai", "y", "yes", "1")
elif type == "date":
value = datetime.strptime(value, "%Y-%m-%d")
value = value.isoformat()
elif type == "float":
value = float(value)
elif type == "integer":
value = int(value)
if type == "string" and value[0] == "[" and value[-1] == "]":
# reference to a set, pick the first value as default
li = json.loads(value)
value = (li[0] if li else None)
elif type == "reference":
value = None
except ValueError as e:
return MSG_CVT % e
model[key]["value"] = value
# scan and modify the history table
# the delete of old key is implicit since they are not copied
query = db.history.id_events == id_event
rows = db(query).select()
for row in rows:
data = {}
rd = (row.data if isinstance(row.data, dict) else dict())
# copy existing key or add new key
for key in model:
data[key] = (rd[key] if key in rd else model[key]["value"])
# modify key name
for oldkey, newkey in update_keys.iteritems():
if oldkey in rd:
data[newkey] = rd[oldkey]
db(db.history.id == row.id).update(data=data)
return MSG % len(rows)
This diff is collapsed.
# -*- coding: utf-8 -*-
"""selector module
"""
import json
from datetime import date, timedelta
from gluon import current
from gluon.dal import Field
from plugin_dbui import Selector
RAWSQL_ACTIVITY = \
"""
SELECT start_date, end_date
FROM history
WHERE id_events=%i AND id_people=%i AND id_domains=%i AND id_teams=%i
ORDER BY start_date, end_date
"""
class SelectorActiveItemsException(BaseException):
pass
class SelectorActiveItems(Selector):
"""Selector to get records active during a given period of time.
The period of time are defined by the selector fields
``year_start`` and ``year_end``.
Args:
table (gluon.dal.Table): the virtual table defining the selector.
exclude_fields (list of string):
name of the selector fields which are
excluded in the query.
"""
def __init__(self, table, exclude_fields=("year_end", "year_start")):
self._cache_period = None
self._period_end = None
self._period_start = None
Selector.__init__(self, table, exclude_fields=exclude_fields)
if self.year_start and not self.year_end:
self._period_start = date(self.year_start, 1, 1)
self._period_end = date(self.year_start, 12, 31)
elif self.year_start and self.year_end:
self._period_start = date(self.year_start, 1, 1)
self._period_end = date(self.year_end, 12, 31)
else:
raise SelectorActiveItemsException("Period is not defined.")
def get_years(self):
"""The list of year between period start and end.
Returns:
list:
"""
if self._cache_period:
start, end = self._cache_period
else:
start = self._period_start.year
end = self._period_end.year
return range(start, end + 1)
def query(self, table):
"""Build the database query for the database *table*.
The query includes inner join for foreign keys, selector constraints
as well as extra queries.
Args:
table (gluon.dal.Table):
the database table in which records are selected.
Returns:
gluon.dal.Query:
The query selects active items during the given period of
time. It only works for table having the database fields
``start_date`` and ``end_date``.
"""
query = Selector.query(self, table)
if self._period_start and self._period_end:
query &= table.start_date <= self._period_end
q2 = table.end_date == None
q3 = table.end_date >= self._period_start
query &= (q2 | q3)
return query
def reset_period(self):
"""Reset the period to the initial values.
"""
if self._cache_period:
self._period_start, self._period_end = self._cache_period
self._cache_period = None
def set_year(self, year):
"""Modify the period attributes in order to query records active
during the given year.
Args:
year (integer):
"""
# keep original period
if self._cache_period is None:
self._cache_period = (self._period_start, self._period_end)
self._period_start = date(year, 1, 1)
self._period_end = date(year, 12, 31)
class EvtSelector(SelectorActiveItems):
"""Main selector to build list, metric or graph according to
the user criteria:
- focus on history record and on a given event.
- 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``.
Args:
table (gluon.dal.Table): the virtual table defining the selector.
exclude_fields (tuple of string):
extend the list selector fields which are
excluded in the query. The field related to people / object
categories, year_end, year_start and year are systematically
excluded.
id_event (int): the event identifier
"""
def __init__(self, table, id_event=0, exclude_fields=()):
li = ["data",
"id_object_categories",
"id_object_code",
"id_people_categories",
"id_people_code",
"year_end",
"year_start"]
li.extend(exclude_fields)
SelectorActiveItems.__init__(self, table, exclude_fields=li)
self._db = db = current.globalenv["db"]
self._id_event = id_event
# add virtual fields
history = db.history
virtual = Field.Virtual
db.people.age = virtual("age", self._age, ftype="integer")
history.coverage = virtual("coverage", self._coverage, ftype="double")
history.duration = virtual("duration", self._duration, ftype="double")
history.fte = virtual("fte", self._fte, ftype="double")
history.is_end = virtual("is_end", self._is_end, ftype="boolean")
history.is_over = virtual("is_over", self._is_over, ftype="boolean")
history.is_start = virtual("is_start", self._is_start, ftype="boolean")
def _active_period(self, id_people, id_domain, id_team):
"""Determine the period when a person is active in a given
domain and team and for a given event
Args:
id_people (int):
id_domain (int):
id_team (int):
Returns
tuple:
start (datetime or None)
stop (datetime or None)
"""
rawsql = RAWSQL_ACTIVITY % (self._id_event,
id_people,
id_domain,
id_team)
rows = self._db.executesql(rawsql)
nrows = len(rows)
if nrows == 1:
return (rows[0][0], rows[0][1])
elif nrows > 1:
return (rows[0][0], rows[-1][1])
else:
return (None, None)
def _age(self, row):
"""Compute the age of the person associated to the history record, now.
Args:
row (gluon.dal.Row): row of the history table.
Returns:
int or None:
"""
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 time coverage of the history record with respect
to the period range.
Args:
row (gluon.dal.Row): row of the history table.
Returns:
float: a value between ``0`` and ``1``.
"""
value = 0.
period_end = self._period_end
period_start = self._period_start
if period_start and period_end:
start = max(period_start, row.history.start_date)
end = period_end
if row.history.end_date:
end = min(period_end, row.history.end_date)
x = (end - start).days
y = (period_end - period_start).days
value = float(x) / float(y)
return value
def _duration(self, row):
"""Compute the duration of the history record.
The end date is ``now`` when the history record is not finished.
Args:
row (gluon.dal.Row): row of the history table.
Returns:
float: in seconds
"""
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 is None and start <= now:
value = now - start
elif start and end and end < now:
value = end - start
return value.total_seconds()
def _fte(self, row):
"""Compute the full time equivalent of the person associated
to the history record. It is the product of the time coverage
and the percentage of the person assigned to the record.
Args:
row (gluon.dal.Row): row of the history table.
Returns:
float: a value between 0 and 1.
"""
value = 0.
if row.history.percentage:
value = self._coverage(row) * (row.history.percentage / 100.)
return value
def _is_end(self, row):
"""Return true is the active period (for a given person, domain and
team) ends during the period range.
Args:
row (gluon.dal.Row): row of the history table.
Returns:
bool:
"""
history = row.history
start, end = self._active_period(history.id_people,
history.id_domains,
history.id_teams)
period_end = self._period_end
period_start = self._period_start
if end and start != end:
return period_start <= end <= period_end
return False
def _is_over(self, row):
"""Return true is the active period (for a given person, domain and
team) is over now.
Args:
row (gluon.dal.Row): row of the history table.
Returns:
bool:
"""
history = row.history
start, end = self._active_period(history.id_people,
history.id_domains,
history.id_teams)
if end and start != end:
return end < date.today()
return False
def _is_start(self, row):
"""Return true is the active period (for a given person, domain and
team) starts during the period range.
Args:
row (gluon.dal.Row): row of the history table.
Returns:
bool:
"""
history = row.history
start, end = self._active_period(history.id_people,
history.id_domains,
history.id_teams)
period_end = self._period_end
period_start = self._period_start
if start and start != end:
return period_start <= start <= period_end
return False
def query(self, table):
"""Supersede the base class method to handle categories constraints.
Args:
table (gluon.dal.Table):
"""
db = table._db
query = SelectorActiveItems.query(self, table)
# history data block
# list of key, value pair: [[None, "v1"], ["k2", "v2"], [None, "v5"]]
# apply the like operator: %bla%bla%
data = json.loads(self.data)
if len(data) > 0:
conditions = []
for key, val in data:
if key is None and val is None:
pass
elif key is None:
conditions.append(val)
elif val is None:
conditions.append('"%s":' % key)
elif val in ("true", "false"):
conditions.append('"%s": %s' % (key, val))
elif isinstance(val, (unicode, str)):
conditions.append('"%s": "%s"' % (key, val))
else:
conditions.append('"%s": %s' % (key, val))
conditions = "%%%s%%" % "%".join(conditions)
query &= db.history.data.like(conditions, case_sensitive=False)
# object category and code
id_object_code = self.id_object_code
if id_object_code: