Skip to content

Commit cb4a84f

Browse files
Merge pull request #36 from 3dem/rc0.0.4
Release 0.0.4
2 parents 48be08a + e6407c3 commit cb4a84f

File tree

11 files changed

+931
-333
lines changed

11 files changed

+931
-333
lines changed

emhub/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from glob import glob
3131

3232

33-
__version__ = '0.0.3'
33+
__version__ = '0.0.4'
3434

3535

3636
def create_app(test_config=None):
@@ -115,6 +115,8 @@ def main():
115115
kwargs['is_devel'] = app.is_devel
116116
kwargs['version'] = __version__
117117
kwargs['emhub_title'] = app.config.get('EMHUB_TITLE', '')
118+
kwargs['possible_owners'] = app.dc.get_pi_labs()
119+
kwargs.update(app.dc.get_resources_list())
118120

119121
return flask.render_template('main.html', **kwargs)
120122

emhub/data/data_content.py

Lines changed: 62 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@ def _dateStr(self, datetime):
5454
def get(self, **kwargs):
5555
content_id = kwargs['content_id']
5656
get_func_name = 'get_%s' % content_id.replace('-', '_') # FIXME
57+
dataDict = {} # self.get_resources_list()
5758
get_func = getattr(self, get_func_name, None)
58-
return {} if get_func is None else get_func(**kwargs)
59+
if get_func is not None:
60+
dataDict.update(get_func(**kwargs))
61+
return dataDict
5962

6063
def get_dashboard(self, **kwargs):
6164
dataDict = self.get_resources_list()
@@ -219,41 +222,7 @@ def get_booking_calendar(self, **kwargs):
219222
for a in dm.get_applications()
220223
if a.is_active]
221224

222-
# Send a list of possible owners of bookings
223-
# 1) Managers or admins can change the ownership to any user
224-
# 2) Application managers can change the ownership to any user in their
225-
# application
226-
# 3) Other users can not change the ownership
227-
user = self.app.user # shortcut
228-
if user.is_manager:
229-
piList = [u for u in dm.get_users() if u.is_pi]
230-
elif user.is_application_manager:
231-
apps = [a for a in user.created_applications if a.is_active]
232-
piSet = {user.get_id()}
233-
piList = [user]
234-
for a in apps:
235-
for pi in a.users:
236-
if pi.get_id() not in piSet:
237-
piList.append(pi)
238-
elif user.is_pi:
239-
piList = [user]
240-
else:
241-
piList = []
242-
243-
def _userjson(u):
244-
return {'id': u.id, 'name': u.name}
245-
246-
# Group users by PI
247-
labs = []
248-
for u in piList:
249-
if u.is_pi:
250-
lab = [_userjson(u)] + [_userjson(u2) for u2 in u.get_lab_members()]
251-
labs.append(lab)
252-
253-
if user.is_manager:
254-
labs.append([_userjson(u) for u in self._get_facility_staff()])
255-
256-
dataDict['possible_owners'] = labs
225+
dataDict['possible_owners'] = self.get_pi_labs()
257226
dataDict['resource_id'] = kwargs.get('resource_id', None)
258227
return dataDict
259228

@@ -405,6 +374,27 @@ def get_portal_import_application(self, **kwargs):
405374

406375
return result
407376

377+
def get_reports_time_distribution(self, **kwargs):
378+
#d = request.json or request.form
379+
d = {'start': '2020-01-01',
380+
'end': '2020-12-31'
381+
}
382+
bookings = self.app.dm.get_bookings_range(
383+
datetime_from_isoformat(d['start']),
384+
datetime_from_isoformat(d['end'])
385+
)
386+
func = self.app.dc.booking_to_event
387+
bookings = [func(b) for b in bookings
388+
if b.resource.is_microscope]
389+
390+
from emhub.reports import get_booking_counters
391+
counters, cem_counters = get_booking_counters(bookings)
392+
393+
return {'overall': counters,
394+
'cem': cem_counters,
395+
'possible_owners': self.get_pi_labs()
396+
}
397+
408398
# --------------------- Internal helper methods ---------------------------
409399
def booking_to_event(self, booking):
410400
""" Return a dict that can be used as calendar Event object. """
@@ -636,3 +626,39 @@ def _import_order_from_portal(self, orderCode):
636626

637627
return app
638628

629+
def get_pi_labs(self):
630+
# Send a list of possible owners of bookings
631+
# 1) Managers or admins can change the ownership to any user
632+
# 2) Application managers can change the ownership to any user in their
633+
# application
634+
# 3) Other users can not change the ownership
635+
user = self.app.user # shortcut
636+
if user.is_manager:
637+
piList = [u for u in self.app.dm.get_users() if u.is_pi]
638+
elif user.is_application_manager:
639+
apps = [a for a in user.created_applications if a.is_active]
640+
piSet = {user.get_id()}
641+
piList = [user]
642+
for a in apps:
643+
for pi in a.users:
644+
if pi.get_id() not in piSet:
645+
piList.append(pi)
646+
elif user.is_pi:
647+
piList = [user]
648+
else:
649+
piList = []
650+
651+
def _userjson(u):
652+
return {'id': u.id, 'name': u.name}
653+
654+
# Group users by PI
655+
labs = []
656+
for u in piList:
657+
if u.is_pi:
658+
lab = [_userjson(u)] + [_userjson(u2) for u2 in u.get_lab_members()]
659+
labs.append(lab)
660+
661+
if user.is_manager:
662+
labs.append([_userjson(u) for u in self._get_facility_staff()])
663+
664+
return labs

emhub/data/data_manager.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -453,27 +453,31 @@ def __validate_booking(self, booking, **kwargs):
453453
check_min_booking = kwargs.get('check_min_booking', True)
454454
check_max_booking = kwargs.get('check_max_booking', True)
455455

456-
user = self._user
457-
if not user.is_manager and not r.is_active:
458-
raise Exception("Selected resource is inactive now. ")
459456

460457
if booking.start >= booking.end:
461458
raise Exception("The booking 'end' should be after the 'start'. ")
462459

463-
if not user.is_manager and booking.start.date() < self.now().date():
464-
raise Exception("The booking 'start' can not be in the past. ")
465-
466-
if check_min_booking and r.min_booking > 0:
467-
mm = dt.timedelta(minutes=int(r.min_booking * 60))
468-
if booking.duration < mm:
469-
raise Exception("The duration of the booking is less that "
470-
"the minimum specified for the resource. ")
460+
user = self._user
471461

472-
if booking.type == 'booking' and check_max_booking and r.max_booking > 0:
473-
mm = dt.timedelta(minutes=int(r.max_booking * 60))
474-
if booking.duration > mm:
475-
raise Exception("The duration of the booking is greater that "
476-
"the maximum allowed for the resource. ")
462+
# The following validations do not apply for managers
463+
if not user.is_manager:
464+
if r.is_active:
465+
raise Exception("Selected resource is inactive now. ")
466+
467+
if booking.start.date() < self.now().date():
468+
raise Exception("The booking 'start' can not be in the past. ")
469+
470+
if check_min_booking and r.min_booking > 0:
471+
mm = dt.timedelta(minutes=int(r.min_booking * 60))
472+
if booking.duration < mm:
473+
raise Exception("The duration of the booking is less that "
474+
"the minimum specified for the resource. ")
475+
476+
if booking.type == 'booking' and check_max_booking and r.max_booking > 0:
477+
mm = dt.timedelta(minutes=int(r.max_booking * 60))
478+
if booking.duration > mm:
479+
raise Exception("The duration of the booking is greater that "
480+
"the maximum allowed for the resource. ")
477481

478482
overlap = self.get_bookings_range(booking.start,
479483
booking.end,

emhub/reports/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
from .time_distribution import get_booking_counters

emhub/reports/time_distribution.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
2+
import re
3+
4+
5+
class Counter:
6+
HEADERS = ["", "Bookings", "Days", "%"]
7+
FORMAT = u"{:>15}{:>10}{:>10}{:>10}"
8+
TOTAL = None
9+
10+
def __init__(self, name, filter=None):
11+
self._name = name
12+
self.counter = 0
13+
self.days = 0
14+
self._filter = filter or self._filter_by_name
15+
16+
def _filter_by_name(self, b):
17+
return self._name.lower() in b['title'].lower()
18+
19+
def count(self, b):
20+
if self._filter(b):
21+
self.counter += 1
22+
self.days += b['days']
23+
return True
24+
return False
25+
26+
27+
class CounterList:
28+
def __init__(self, *names):
29+
self._counters = [
30+
Counter('Total', lambda b: True),
31+
Counter('Reminder', lambda b: True)
32+
]
33+
self.reminder = []
34+
35+
for n in names:
36+
self.addCounter(n)
37+
38+
def addCounter(self, counter):
39+
c = counter if isinstance(counter, Counter) else Counter(str(counter))
40+
self._counters.insert(-1, c)
41+
42+
def count(self, b):
43+
self._counters[0].count(b) # Always count total
44+
45+
def _any():
46+
for c in self._counters[1:-1]:
47+
if c.count(b):
48+
return True
49+
return False
50+
51+
if not _any():
52+
self.reminder.append(b)
53+
self._counters[-1].count(b) # Count reminder
54+
55+
def data(self):
56+
total = self._counters[0].days
57+
data = [[c._name, c.counter, c.days,
58+
'%0.2f' % (c.days * 100 / total)]
59+
for c in self._counters]
60+
61+
return data
62+
63+
def print(self):
64+
format = Counter.FORMAT.format
65+
print(format(*Counter.HEADERS))
66+
for row in self.data():
67+
print(format(*row))
68+
69+
def printReminder(self):
70+
for b in self.reminder:
71+
print(b['title'])
72+
73+
74+
def is_maintainance(b):
75+
t = b['title'].lower()
76+
return any(k in t for k in ['cycle', 'installation', 'maintenance', 'afis'])
77+
78+
def is_developmnt(b):
79+
t = b['title'].lower()
80+
return any(k in t for k in ['method', 'research', 'tests', 'mikroed', 'microed'])
81+
82+
83+
def get_cem(b):
84+
title = b['title'].upper()
85+
86+
# Take only the first part of the application label
87+
# (because it can contains the alias in parenthesis)
88+
m = re.search("(CEM([0-9])+)", title)
89+
90+
if m is not None:
91+
parsedCem = m.group(1).upper().strip()
92+
# Enforce numeric part is exactly 5 digits
93+
cemNumber = m.group(2)
94+
n = len(cemNumber)
95+
if n < 5:
96+
cemNumber = "0" * (5 - n) + cemNumber
97+
else:
98+
cemNumber = cemNumber[-5:]
99+
100+
return 'CEM' + cemNumber
101+
102+
return None
103+
104+
105+
class CemCounter(Counter):
106+
def _filter_by_name(self, b):
107+
return self._name.upper() == get_cem(b)
108+
109+
110+
def get_booking_counters(bookings):
111+
maintainance = Counter('Maintenance', is_maintainance)
112+
development = Counter('Developmnt', is_developmnt)
113+
CEM = Counter('CEM', filter=lambda b: get_cem(b) is not None)
114+
115+
counters = CounterList('Downtime', maintainance, 'DBB', CEM, development)
116+
cem_counters = CounterList()
117+
118+
cem_dict = {}
119+
120+
for b in bookings:
121+
title = b['title']
122+
123+
if 'Ume' in title:
124+
continue
125+
126+
counters.count(b)
127+
128+
cem = get_cem(b)
129+
130+
if cem is not None:
131+
c = cem_dict.get(cem, 0)
132+
cem_dict[cem] = c + 1
133+
if not c:
134+
cem_counters.addCounter(CemCounter(cem))
135+
cem_counters.count(b)
136+
137+
return counters, cem_counters
138+
139+
140+

0 commit comments

Comments
 (0)