Commit 6cade790 authored by LE GAC Renaud's avatar LE GAC Renaud
Browse files

Polish the interface and add more protections, metrics.

parent 959d76db
......@@ -35,12 +35,15 @@ def hardware():
return dict(footer='', header=header, rows=rows)
def history():
"""History for a person.
def list():
"""list of objects followed by the history table.
"""
# instantiate the selector
selector = MySelector(virtdb.history_selector)
selector = MySelector(virtdb.list_selector,
exclude_fields=('year',
'period_start',
'period_end'))
iframe = selector.download()
if iframe:
......@@ -52,7 +55,7 @@ def history():
# retrieve the records
orderfields=(db.people.last_name, db.history.start_date)
rows = selector.select(db.history, orderby=orderfields)
rows = selector.select_active_items(db.history, orderby=orderfields)
# build the header and publish
header = '%s %s' % (T('History'), selector.header(db))
......
""" Metric controllers
"""
from reporting_tools import MySelector, Person
from reporting_tools import MySelector, CountPeople
BASE_VIEW = 'report.%s'
def people():
"""Count the number of active people for a given period of time,
given team, ...
def people_per_category():
"""Count the number of active people per category during a given
period of time, for a given team, ...
"""
selector = MySelector(virtdb.people_selector,
exclude_fields=('category',
'year',
'period_start',
'period_end'))
'period_end',
'year'))
iframe = selector.download()
if iframe:
return iframe
# add constraint to avoid event diploma, distinction,...
# and to select category of people
selector.append_query(db.history.id_events == undef_id)
field = db.people_categories.category
count = CountPeople(field)
rows = count(selector)
# get the header and publish
header = '%s %s' % (T('People per category '), selector.header(db))
response.view = BASE_VIEW % request.extension
return dict(field=field, footer='', header=header, rows=rows)
def people_per_quality():
"""Count the number of active people per quality during a given
period of time, for a given team, ...
"""
selector = MySelector(virtdb.people_selector,
exclude_fields=('category',
'period_start',
'period_end',
'year'))
iframe = selector.download()
if iframe:
return iframe
if selector.category:
selector.append_query(db.people_categories.category == selector.category)
# count the number of people per category
query = selector.query_active_items(db.history)
rows = db(query).select(db.people_categories.category,
db.people.id.count(distinct=True),
groupby=db.people_categories.category,
orderby=db.people_categories.category)
field = db.people_categories.code
count = CountPeople(field)
rows = count(selector)
# count the number of FTE per category
# NOTE: aggregation does not work with virtual field
# it is why it done by hand
tool = Person(selector)
db.history.time_coverage = Field.Virtual(tool.coverage)
# get the header and publish
header = '%s %s' % (T('People per quality '), selector.header(db))
response.view = BASE_VIEW % request.extension
coverage, fte = {}, {}
mycat = None
orderby = db.people_categories.category
return dict(field=field, footer='', header=header, rows=rows)
def people_per_team():
"""Count the number of active people per team during a given
period of time, for a given category, ...
for el in selector.select_active_items(db.history, orderby=orderby):
if el.people_categories.category != mycat:
mycat = el.people_categories.category
coverage[mycat] = 0.
fte[mycat] = 0.
"""
selector = MySelector(virtdb.people_selector,
exclude_fields=('category',
'period_start',
'period_end',
'year'))
coverage[mycat] += el.history.time_coverage
fte[mycat] += el.history.time_coverage * el.history.percentage * 0.01
iframe = selector.download()
if iframe:
return iframe
field = db.teams.team
count = CountPeople(field)
rows = count(selector)
# add the FTE columns to the rows table
rows.colnames.extend(['coverage', 'FTE'])
for row in rows.records:
mycat = row.people_categories.category
row._extra['coverage'] = round(coverage[mycat], 2)
row._extra['FTE'] = round(fte[mycat], 2)
# get the header and publish
header = '%s %s' % (T('List of people '), selector.header(db))
header = '%s %s' % (T('People per team '), selector.header(db))
response.view = BASE_VIEW % request.extension
return dict(footer='', header=header, rows=rows)
return dict(field=field, footer='', header=header, rows=rows)
......@@ -23,6 +23,7 @@
'auth_user': 'auth_user',
'Birth Date': 'Né le',
'budgets': 'budgets',
"Can't delete this record since several transactions refer to it.": 'Cet enregistrement ne peut pas être détruit car il est utilisé dans la table historique.',
'Cannot be empty': 'Cannot be empty',
'careers': 'carrières',
'CAS': 'CAS',
......@@ -85,8 +86,8 @@
'Forms': 'Formulaire',
'FTE': 'FTE',
'Funding': 'Financement',
'Fundings': 'Finacements',
'fundings': 'financements',
'Fundings': 'Financements',
'General': 'Général',
'Grade': 'Grade',
'greater or equal to': 'plus grand ou égual à',
......@@ -117,11 +118,13 @@
'levels': 'niveaux',
'Line': 'Ligne',
'lines': 'lines',
'List': 'Liste',
'List of hardware': 'Liste de matériel',
'List of people': 'Liste de personne',
'List of people ': 'Liste des personnes ',
'List of responsibilities': 'Liste des responsabilités',
'List of trainee': 'Liste des stagiaires',
'Liste': 'Liste',
'Lists': 'Lists',
'Logged in': 'Logged in',
'Login': 'Login',
......@@ -144,14 +147,21 @@
'Origin': 'Origin',
'Password': 'Password',
"Password fields don't match": "Password fields don't match",
'people': 'personnel',
'People': 'Personnel',
'people': 'personnes',
'People': 'Personnes',
'People per category': 'Personnes par catégorie',
'People per category ': 'Personnes par catégorie ',
'People per quality': 'Personnes par qualité',
'People per quality ': 'Personnes par qualité ',
'People per team': 'Personnes par équipe',
'People per team ': 'Personnes par équipe ',
'people_categories': 'catégories de personnel',
'Percentage': 'Pourcentage',
'Period': 'Periode',
'Period': 'Période',
'Period End': 'Period End',
'Period Start': 'Period Start',
'Person': 'Persone',
'Persone': 'Persone',
'Phd': 'Phd',
'PHD': 'PHD',
'PhD': 'PhD',
......@@ -163,6 +173,7 @@
'Project': 'Projet',
'project leader,...': 'project leader,...',
'projects': 'projets',
'Projet': 'Projet',
'Quality': 'Qualité',
'Qualité': 'Qualité',
'Ratio': 'Ratio',
......@@ -216,4 +227,5 @@
'Year': 'Année',
'Year End': 'Year End',
'Year Start': 'Year Start',
'Équipe': 'Équipe',
}
......@@ -8,6 +8,7 @@ import locale
import plugin_dbui as dbui
from datetime import datetime
from callbacks import INHIBIT_CASCADE_DELETE
from gluon.tools import PluginManager
from my_validators import IS_IN_USET
......@@ -58,11 +59,15 @@ db.define_table("events",
Field("definition", "text"),
migrate="events.table")
db.events._before_delete.append(INHIBIT_CASCADE_DELETE)
db.define_table("fundings",
Field("agency", "string", notnull=True, unique=True),
Field("definition", "text"),
migrate="fundings.table")
db.fundings._before_delete.append(INHIBIT_CASCADE_DELETE)
db.define_table("history",
Field("id_people", "reference people", default=undef_id, label='Person'),
Field("id_teams", "reference teams", default=undef_id, label='Team'),
......@@ -94,11 +99,15 @@ db.define_table("organizations",
Field("definition", "text"),
migrate="organizations.table")
db.organizations._before_delete.append(INHIBIT_CASCADE_DELETE)
db.define_table("organization_levels",
Field("level", "string", notnull=True, unique=True),
Field("definition", "text"),
migrate="organization_levels.table")
db.organization_levels._before_delete.append(INHIBIT_CASCADE_DELETE)
db.define_table("people",
Field("first_name", "string", notnull=True),
Field("last_name", "string", notnull=True),
......@@ -107,17 +116,25 @@ db.define_table("people",
Field("note", "text"),
migrate="people.table")
db.people._before_delete.append(INHIBIT_CASCADE_DELETE)
db.define_table("people_categories",
Field("code", "string", notnull=True, unique=True, label=T("Quality")),
Field("category", "string", notnull=True),
Field("definition", "text"),
migrate="people_categories.table")
db.people_categories._before_delete.append(INHIBIT_CASCADE_DELETE)
db.define_table("projects",
Field("project", "string", notnull=True, unique=True),
migrate="projects.table")
db.projects._before_delete.append(INHIBIT_CASCADE_DELETE)
db.define_table("teams",
Field("team", "string", notnull=True, unique=True),
Field("domain", "string"),
migrate="teams.table")
db.teams._before_delete.append(INHIBIT_CASCADE_DELETE)
......@@ -9,13 +9,6 @@ FORMATS = ['csv', 'html', 'pdf', 'tex']
virtdb = DAL(None)
#
# my team
#
id_lhcb = dbui.get_id(db.teams, team='LHCb')
if not id_lhcb:
id_lhcb = undef_id
#
# hardware selector
#
......@@ -23,9 +16,9 @@ virtdb.define_table('hardware_selector',
Field('year', 'integer', default=year),
Field('period_start', 'date'),
Field('period_end', 'date'),
Field('id_people', 'reference people', label='Person'),
Field('id_teams', 'reference teams', default=id_lhcb, label='Team'),
Field('id_projects', 'reference projects', label='Project'),
Field('id_people', 'reference people', label=T('Person')),
Field('id_teams', 'reference teams', label=T('Team')),
Field('id_projects', 'reference projects', label=T('Project')),
Field('format', 'string', default='html'))
virtdb.hardware_selector.id_people.requires = IS_IN_DB(db, 'people.last_name')
......@@ -35,21 +28,24 @@ virtdb.hardware_selector.id_teams.requires = IS_IN_DB(db, 'teams.team')
virtdb.hardware_selector.format.requires = IS_IN_SET(FORMATS)
#
# history selector
# list selector
#
virtdb.define_table('history_selector',
Field('id_people', 'reference people', label='Person'),
Field('id_teams', 'reference teams', label='Team'),
Field('id_projects', 'reference projects', label='Project'),
Field('id_events', 'reference events', label='Event'),
virtdb.define_table('list_selector',
Field('year', 'integer', default=year),
Field('period_start', 'date'),
Field('period_end', 'date'),
Field('id_people', 'reference people', label=T('Person')),
Field('id_teams', 'reference teams', label=T('Team')),
Field('id_projects', 'reference projects', label=T('Project')),
Field('id_events', 'reference events', label=T('List')),
Field('format', 'string', default='html'))
virtdb.history_selector.id_events.requires = IS_IN_DB(db, 'events.event')
virtdb.history_selector.id_people.requires = IS_IN_DB(db, 'people.last_name')
virtdb.history_selector.id_projects.requires = IS_IN_DB(db, 'projects.project')
virtdb.history_selector.id_teams.requires = IS_IN_DB(db, 'teams.team')
virtdb.list_selector.id_events.requires = IS_IN_DB(db, 'events.event')
virtdb.list_selector.id_people.requires = IS_IN_DB(db, 'people.last_name')
virtdb.list_selector.id_projects.requires = IS_IN_DB(db, 'projects.project')
virtdb.list_selector.id_teams.requires = IS_IN_DB(db, 'teams.team')
virtdb.history_selector.format.requires = IS_IN_SET(FORMATS)
virtdb.list_selector.format.requires = IS_IN_SET(FORMATS)
#
# people selector
......@@ -59,8 +55,9 @@ virtdb.define_table('people_selector',
Field('period_start', 'date'),
Field('period_end', 'date'),
Field('category', 'string'),
Field('id_teams', 'reference teams', default=id_lhcb, label='Team'),
Field('id_projects', 'reference projects', label='Project'),
Field('id_people_categories', 'reference people_categories', label= T("Quality")),
Field('id_teams', 'reference teams', label=T('Team')),
Field('id_projects', 'reference projects', label=T('Project')),
Field('format', 'string', default='html'))
rows = db().select(db.people_categories.category,
......@@ -70,6 +67,7 @@ rows = db().select(db.people_categories.category,
virtdb.people_selector.category.requires = \
IS_IN_SET((el.category for el in rows))
virtdb.people_selector.id_people_categories.requires = IS_IN_DB(db, 'people_categories.code')
virtdb.people_selector.id_projects.requires = IS_IN_DB(db, 'projects.project')
virtdb.people_selector.id_teams.requires = IS_IN_DB(db, 'teams.team')
......@@ -85,9 +83,9 @@ virtdb.define_table('person_wizard',
Field("birth_date", "date"),
Field("start_date", "date"),
Field("end_date", "date"),
Field("id_people_categories", 'reference people_categories', default=undef_id, label='Category'),
Field("id_teams", 'reference teams', default=id_lhcb, label='Team'),
Field("id_projects", 'reference projects', default=undef_id, label='Project'))
Field("id_people_categories", 'reference people_categories', default=undef_id, label=T('Category')),
Field("id_teams", 'reference teams', label=T('Team')),
Field("id_projects", 'reference projects', default=undef_id, label=T('Project')))
virtdb.person_wizard.id_people_categories.requires = \
IS_IN_DB(db, 'people_categories.code')
......@@ -102,8 +100,8 @@ virtdb.define_table('responsibilities_selector',
Field('year', 'integer', default=year),
Field('period_start', 'date'),
Field('period_end', 'date'),
Field('id_people', 'reference people', label='Person'),
Field('id_organization_levels', 'reference organization_levels', label='Level'),
Field('id_people', 'reference people', label=T('Person')),
Field('id_organization_levels', 'reference organization_levels', label=T('Level')),
Field('format', 'string', default='html'))
virtdb.responsibilities_selector.id_people.requires = \
......@@ -142,8 +140,8 @@ virtdb.define_table('trainee_wizard',
Field("trainee_title", "text", label=T('Title')),
Field("trainee_university", "text", label=T('University')),
Field("trainee_category", "string", default='PHY', label=T("Domain")),
Field("id_teams", 'reference teams', default=id_lhcb, label='Team'),
Field("id_projects", 'reference projects', default=undef_id, label='Project'))
Field("id_teams", 'reference teams', label='Team'),
Field("id_projects", 'reference projects', default=undef_id, label=T('Project')))
virtdb.trainee_wizard.id_projects.requires = IS_IN_DB(db, 'projects.project')
virtdb.trainee_wizard.id_teams.requires = IS_IN_DB(db, 'teams.team')
......
......@@ -19,16 +19,23 @@ fieldsModifier.configure_field('start_date', flex=1)
fieldsModifier.configure_field('end_date', flex=1)
fieldsModifier.merge_fields('start_date', 'end_date', fieldLabel=T('Period'))
# select a person by typing first letters of his name in the combobox
# select a value in combox by typing the first letters
fieldsModifier.configure_field('id_events', editable='false', mode='local')
fieldsModifier.configure_field('id_people_categories', editable='false', mode='local')
fieldsModifier.configure_field('id_people', editable='false', mode='local')
fieldsModifier.configure_field('id_projects', editable='false', mode='local')
fieldsModifier.configure_field('id_teams', editable='false', mode='local')
fieldsModifier.configure_field('note', height=150)
#
# history selector
# list selector
#
fieldsModifier = dbui.FieldsModifier('history_selector')
fieldsModifier = dbui.FieldsModifier('list_selector')
fieldsModifier.configure_field('id_people', editable='false', mode='local')
fieldsModifier.configure_field('period_start', flex=1)
fieldsModifier.configure_field('period_end', flex=1)
fieldsModifier.merge_fields('period_start', 'period_end', fieldLabel=T('Period'))
#
# people selector
......@@ -46,3 +53,11 @@ fieldsModifier.configure_field('id_people', editable='false', mode='local')
fieldsModifier.configure_field('period_start', flex=1)
fieldsModifier.configure_field('period_end', flex=1)
fieldsModifier.merge_fields('period_start', 'period_end', fieldLabel=T('Period'))
#
# trainee selector
#
fieldsModifier = dbui.FieldsModifier('trainee_selector')
fieldsModifier.configure_field('period_start', flex=1)
fieldsModifier.configure_field('period_end', flex=1)
fieldsModifier.merge_fields('period_start', 'period_end', fieldLabel=T('Period'))
......@@ -52,44 +52,49 @@ gridNode.add_children(db.tables, func=configurator, hidden=hidden_tables)
#
# report node
#
countPeopleLeaf = dbui.to_panelWithUrlSelector(virtdb.people_selector,
baseUrl=URL('metric', 'people'))
url = URL('list', 'hardware')
hardwareLeaf = dbui.to_panelWithUrlSelector(virtdb.hardware_selector, baseUrl=url)
url = URL('list', 'list')
listLeaf = dbui.to_panelWithUrlSelector(virtdb.list_selector, baseUrl=url)
hardwareLeaf = dbui.to_panelWithUrlSelector(virtdb.hardware_selector,
baseUrl=URL('list', 'hardware'))
url = URL('list', 'people')
listPeopleLeaf = dbui.to_panelWithUrlSelector(virtdb.people_selector, baseUrl=url)
url = URL('list', 'responsibilities')
respLeaf = dbui.to_panelWithUrlSelector(virtdb.responsibilities_selector, baseUrl=url)
historyLeaf = dbui.to_panelWithUrlSelector(virtdb.history_selector,
baseUrl=URL('list', 'history'))
url = URL('list', 'trainee')
traineeLeaf = dbui.to_panelWithUrlSelector(virtdb.trainee_selector, baseUrl=url)
listPeopleLeaf = dbui.to_panelWithUrlSelector(virtdb.people_selector,
baseUrl=URL('list', 'people'))
url = URL('metric', 'people_per_category')
peopleCategoryLeaf = dbui.to_panelWithUrlSelector(virtdb.people_selector, baseUrl=url)
respLeaf = dbui.to_panelWithUrlSelector(virtdb.responsibilities_selector,
baseUrl=URL('list', 'responsibilities'))
url = URL('metric', 'people_per_quality')
peopleQualityLeaf = dbui.to_panelWithUrlSelector(virtdb.people_selector, baseUrl=url)
traineeLeaf = dbui.to_panelWithUrlSelector(virtdb.trainee_selector,
baseUrl=URL('list', 'trainee'))
url = URL('metric', 'people_per_team')
peopleTeamLeaf = dbui.to_panelWithUrlSelector(virtdb.people_selector, baseUrl=url)
reportNode = dbui.Node(T('Reports'))
reportNode.add_child(T('Count people'), countPeopleLeaf)
reportNode.add_child(T('History'), historyLeaf)
reportNode.add_child(T('List'), listLeaf)
reportNode.add_child(T('List of hardware'), hardwareLeaf)
reportNode.add_child(T('List of people'), listPeopleLeaf)
reportNode.add_child(T('List of responsibilities'), respLeaf)
reportNode.add_child(T('List of trainee'), traineeLeaf)
reportNode.add_child(T('People per category'), peopleCategoryLeaf)
reportNode.add_child(T('People per quality'), peopleQualityLeaf)
reportNode.add_child(T('People per team'), peopleTeamLeaf)
reportNode.sort_children()
#
# Wizards node
#
personLeaf = dbui.to_panelWithUrlSelector(virtdb.person_wizard,
baseUrl=URL('wizard', 'person'))
url = URL('wizard', 'person')
personLeaf = dbui.to_panelWithUrlSelector(virtdb.person_wizard, baseUrl=url)
traineeLeaf = dbui.to_panelWithUrlSelector(virtdb.trainee_wizard,
baseUrl=URL('wizard', 'trainee'))
url = URL('wizard', 'trainee')
traineeLeaf = dbui.to_panelWithUrlSelector(virtdb.trainee_wizard, baseUrl=url)
wizardNode = dbui.Node(T('Wizards'))
wizardNode.add_child(T('Add person'), personLeaf)
......
""" set of callbacks
@author: R. Le Gac
"""
from gluon import current
from plugin_dbui import CALLBACK_ERRORS, get_where_query
def INHIBIT_CASCADE_DELETE(set):
"""Inhibit the delete when at least one history row uses
the reference field.
@type set: gluon.dal.Set
@param set:
@rtype: bool
@return:
"""
db, T = current.globalenv['db'], current.T
field = set.query.first
# protection
# the query of the set should be "table.id == 45"
if field._db._adapter.EQ != set.query.op:
return False
# protection
# check that the table is ones of the transactions reference tables
tables = (db.events,
db.fundings,
db.organizations,
db.organization_levels,
db.people,
db.people_categories,
db.projects,
db.teams)
if field._table not in tables:
return False
# inhibit the delete if at least one history row use the reference field
query = get_where_query(db.history)
query = (query) & (set.query)
if db(query).count():
field._table[CALLBACK_ERRORS] = \
T("Can't delete this record since several transactions refer to it.")
return True
return False
\ No newline at end of file
......@@ -3,12 +3,112 @@
"""
from datetime import date, datetime, timedelta
from gluon import current
from plugin_dbui import (get_foreign_field,
from gluon.dal import Field
from plugin_dbui import (UNDEF_ID,
get_foreign_field,
is_foreign_field,
is_table_with_foreign_fields,
Selector)
class Base(object):
"""Base class for reporting tool.
"""
def __init__(self, selector):
now = datetime.now()
self.now = date(now.year, now.month, now.day)
self.period_start = selector.period_start
self.period_end = selector.period_end
def duration(self, row):
"""Compute the duration of an item.
Return a datetime.TimeDelta
"""
start = row.history.start_date
end = row.history.end_date
now = self.now
if start and end and start <= now <= end: