Skip to content

Commit 39ff71b

Browse files
authored
Merge pull request #467 from Espurna/task/424-dashboard-all-mine
Dashboard all/mine filters
2 parents 61f5dbe + af67c4f commit 39ff71b

File tree

4 files changed

+237
-15
lines changed

4 files changed

+237
-15
lines changed

bika/lims/browser/dashboard/dashboard.py

+111-11
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,103 @@
33
# Copyright 2011-2016 by it's authors.
44
# Some rights reserved. See LICENSE.txt, AUTHORS.txt.
55

6+
import datetime
7+
import json
8+
from calendar import monthrange
9+
10+
from DateTime import DateTime
11+
from Products.Archetypes.public import DisplayList
612
from Products.CMFCore.utils import getToolByName
713
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
14+
15+
from bika.lims import bikaMessageFactory as _
16+
from bika.lims import logger
817
from bika.lims.browser import BrowserView
9-
from bika.lims.catalog import CATALOG_ANALYSIS_REQUEST_LISTING
1018
from bika.lims.catalog import CATALOG_ANALYSIS_LISTING
19+
from bika.lims.catalog import CATALOG_ANALYSIS_REQUEST_LISTING
1120
from bika.lims.catalog import CATALOG_WORKSHEET_LISTING
12-
from bika.lims import bikaMessageFactory as _
13-
from bika.lims import logger
14-
from calendar import monthrange
15-
from DateTime import DateTime
16-
import plone
17-
import json
18-
import datetime
21+
from bika.lims.utils import get_strings
22+
23+
DASHBOARD_FILTER_COOKIE = 'dashboard_filter_cookie'
1924

2025

2126
class DashboardView(BrowserView):
2227
template = ViewPageTemplateFile("templates/dashboard.pt")
2328

29+
def __init__(self, context, request):
30+
BrowserView.__init__(self, context, request)
31+
self.dashboard_cookie = None
32+
self.member = None
33+
2434
def __call__(self):
2535
tofrontpage = True
2636
mtool=getToolByName(self.context, 'portal_membership')
2737
if not mtool.isAnonymousUser() and self.context.bika_setup.getDashboardByDefault():
2838
# If authenticated user with labman role,
2939
# display the Main Dashboard view
3040
pm = getToolByName(self.context, "portal_membership")
31-
member = pm.getAuthenticatedMember()
32-
roles = member.getRoles()
41+
self.member = pm.getAuthenticatedMember()
42+
roles = self.member.getRoles()
3343
tofrontpage = 'Manager' not in roles and 'LabManager' not in roles
3444

35-
if tofrontpage == True:
45+
if tofrontpage:
3646
self.request.response.redirect(self.portal_url + "/bika-frontpage")
3747
else:
3848
self._init_date_range()
49+
self.dashboard_cookie = self.check_dashboard_cookie()
3950
return self.template()
4051

52+
def check_dashboard_cookie(self):
53+
"""
54+
Check if the dashboard cookie should exist through bikasetup
55+
configuration.
56+
57+
If it should exist but doesn't exist yet, the function creates it
58+
with all values as default.
59+
If it should exist and already exists, it returns the value.
60+
Otherwise, the function returns None.
61+
62+
:return: a dictionary of strings
63+
"""
64+
# Getting cookie
65+
cookie_raw = self.request.get(DASHBOARD_FILTER_COOKIE, None)
66+
# If it doesn't exist, create it with default values
67+
if cookie_raw is None:
68+
cookie_raw = self._create_raw_data()
69+
self.request.response.setCookie(
70+
DASHBOARD_FILTER_COOKIE,
71+
json.dumps(cookie_raw),
72+
quoted=False,
73+
path='/')
74+
return cookie_raw
75+
return get_strings(json.loads(cookie_raw))
76+
77+
def is_filter_selected(self, selection_id, value):
78+
"""
79+
Compares whether the 'selection_id' parameter value saved in the
80+
cookie is the same value as the "value" parameter.
81+
82+
:param selection_id: a string as a dashboard_cookie key.
83+
:param value: The value to compare against the value from
84+
dashboard_cookie key.
85+
:return: Boolean.
86+
"""
87+
selected = self.dashboard_cookie.get(selection_id)
88+
return selected == value
89+
90+
def _create_raw_data(self):
91+
"""
92+
Gathers the different sections ids and creates a string as first
93+
cookie data.
94+
95+
:return: A dictionary like:
96+
{'analyses':'all','analysisrequest':'all','worksheets':'all'}
97+
"""
98+
result = {}
99+
for section in self.get_sections():
100+
result[section.get('id')] = 'all'
101+
return result
102+
41103
def _init_date_range(self):
42104
""" Sets the date range from which the data must be retrieved.
43105
Sets the values to the class parameters 'date_from',
@@ -130,6 +192,17 @@ def get_sections(self):
130192
self.get_worksheets_section()]
131193
return sections
132194

195+
def get_filter_options(self):
196+
"""
197+
Returns dasboard filter options.
198+
:return: Boolean
199+
"""
200+
dash_opt = DisplayList((
201+
('all', _('All')),
202+
('mine', _('Mine')),
203+
))
204+
return dash_opt
205+
133206
def _getStatistics(self, name, description, url, catalog, criterias, total):
134207
out = {'type': 'simple-panel',
135208
'name': name,
@@ -163,6 +236,10 @@ def get_analysisrequests_section(self):
163236
cookie_dep_uid = self.request.get('filter_by_department_info', '').split(',') if filtering_allowed else ''
164237
query['getDepartmentUIDs'] = { "query": cookie_dep_uid,"operator":"or" }
165238

239+
# Check if dashboard_cookie contains any values to query
240+
# elements by
241+
query = self._update_criteria_with_filters(query, 'analysisrequests')
242+
166243
# Active Analysis Requests (All)
167244
total = len(catalog(query))
168245

@@ -256,6 +333,10 @@ def get_worksheets_section(self):
256333
cookie_dep_uid = self.request.get('filter_by_department_info', '').split(',') if filtering_allowed else ''
257334
query['getDepartmentUIDs'] = { "query": cookie_dep_uid,"operator":"or" }
258335

336+
# Check if dashboard_cookie contains any values to query
337+
# elements by
338+
query = self._update_criteria_with_filters(query, 'worksheets')
339+
259340
# Active Worksheets (all)
260341
total = len(bc(query))
261342

@@ -316,6 +397,9 @@ def get_analyses_section(self):
316397
cookie_dep_uid = self.request.get('filter_by_department_info', '').split(',') if filtering_allowed else ''
317398
query['getDepartmentUID'] = { "query": cookie_dep_uid,"operator":"or" }
318399

400+
# Check if dashboard_cookie contains any values to query elements by
401+
query = self._update_criteria_with_filters(query, 'analyses')
402+
319403
# Active Analyses (All)
320404
total = len(bc(query))
321405

@@ -522,3 +606,19 @@ def _fill_dates_evo(self, catalog, query):
522606
del o[r]
523607

524608
return outevo
609+
610+
def _update_criteria_with_filters(self, query, section_name):
611+
"""
612+
This method updates the 'query' dictionary with the criteria stored in
613+
dashboard cookie.
614+
615+
:param query: A dictionary with search criteria.
616+
:param section_name: The dashboard section name
617+
:return: The 'query' dictionary
618+
"""
619+
if self.dashboard_cookie is None:
620+
return query
621+
cookie_criteria = self.dashboard_cookie.get(section_name)
622+
if cookie_criteria == 'mine':
623+
query['Creator'] = self.member.getId()
624+
return query
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"All" and "Mine" selection functionality:
2+
=========================================
3+
4+
A selection list with the values 'All' and 'Mine' will be added above each
5+
section of the Dashboard. The panels and information within each panel will
6+
change according to the option selected: for example, if the user selects
7+
'Mine', the panel with the summary of worksheets and worksheets statuses will
8+
only take into account the worksheets to which the user is assigned.
9+
10+
Panels visibility
11+
=================
12+
13+
The labmanager will be able to configure the visibility of each panel on a
14+
user-role basis directly from the dashboard (configuration option
15+
embedded in each panel).

bika/lims/browser/dashboard/templates/dashboard.pt

+80-4
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,33 @@
2525
.dashboard-section {
2626
padding: 20px 0 0;
2727
}
28-
29-
.dashboard-section h2 {
28+
.dashboard-section-head {
3029
background-color: #ccc;
3130
border-radius: 5px 5px 0 0;
31+
padding: 3px 15px;
32+
}
33+
.dashboard-section-head div.actions {
34+
float:right;
35+
text-align:right;
36+
width:30%;
37+
}
38+
.dashboard-section-head div.actions select {
39+
padding: 0 5px;
40+
vertical-align: top;
41+
}
42+
.dashboard-section-head:after {
43+
clear: both;
44+
content: " ";
45+
display: block;
46+
font-size: 0;
47+
height: 0;
48+
visibility: hidden;
49+
}
50+
.dashboard-section h2 {
51+
float:left;
3252
font-size: 1.4em;
3353
font-weight: bold;
34-
padding: 3px 15px;
54+
width:70%;
3555
}
3656
.dashboard-section .h2-legend {
3757
font-size: 0.9em;
@@ -194,6 +214,7 @@
194214
$(document).ready(function(){
195215
loadPieCharts();
196216
loadBarCharts();
217+
dashboard_cookie_controller();
197218
});
198219

199220
function loadBarCharts() {
@@ -389,12 +410,67 @@
389410
.attr("d", arc);
390411
});
391412
}
413+
function dashboard_cookie_controller() {
414+
/* Links a linker controller to each dashboard filter selector.
415+
This controller reads the cookie data once the selector has been
416+
modified and updates its data.
417+
*/
418+
var selector;
419+
var selector_section_id;
420+
var cookie;
421+
var counter;
422+
var cookie_id = 'dashboard_filter_cookie'
423+
var filter_selectors = $('select.dashboard_filters');
424+
for(counter = 0; counter < filter_selectors.length; counter++){
425+
selector = filter_selectors[counter];
426+
$(selector).live('change', function(){
427+
section_id = $(this).attr('section_id');
428+
selected = $(this).find(':selected').val();
429+
cookie_data = render_cookie_data(readCookie(cookie_id));
430+
cookie_data[section_id] = selected;
431+
cookie_data = build_cookie_data(cookie_data);
432+
setCookie('dashboard_filter_cookie', cookie_data);
433+
window.location.reload(true);
434+
});
435+
}
436+
};
437+
/*TODO: I can't call the function defined in bika.lims.site.setCookie*/
438+
function setCookie(cname, cvalue) {
439+
var d = new Date();
440+
d.setTime(d.getTime() + (1 * 24 * 60 * 60 * 1000));
441+
var expires = "expires="+d.toUTCString();
442+
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
443+
};
444+
function render_cookie_data(data){
445+
return JSON.parse(data);
446+
};
447+
function build_cookie_data(data){
448+
return JSON.stringify(data);
449+
};
392450
}(jQuery));
393451
</script>
394452

395453
<tal:section repeat="section python:view.get_sections()">
396454
<div class='dashboard-section'>
397-
<h2 tal:content="section/title" class='dashboard-section-title'></h2>
455+
<div class='dashboard-section-head'>
456+
<h2 tal:content="section/title" class='dashboard-section-title'></h2>
457+
<div class="actions">
458+
<select class="dashboard_filters"
459+
tal:attributes="section_id section/id"
460+
tal:define="section_id section/id">
461+
<tal:dashboard_filter_opts
462+
define="options python: view.get_filter_options()"
463+
repeat="option options">
464+
<option
465+
tal:attributes="
466+
value python:option;
467+
selected python: 'selected' if view.is_filter_selected(section_id, option) else '';"
468+
tal:content="python:options.getValue(option)">
469+
</option>
470+
</tal:dashboard_filter_opts>
471+
</select>
472+
</div>
473+
</div>
398474
<div class='dashboard-panels'>
399475

400476
<!-- Bar-chart panels -->

bika/lims/utils/__init__.py

+31
Original file line numberDiff line numberDiff line change
@@ -774,3 +774,34 @@ def to_int(value, default=0):
774774
return int(value)
775775
except (TypeError, ValueError):
776776
return to_int(default, default=0)
777+
778+
779+
def get_strings(data):
780+
"""
781+
Convert unicode values to strings even if they belong to lists or dicts.
782+
:param data: an object.
783+
:return: The object with all unicode values converted to string.
784+
"""
785+
# if this is a unicode string, return its string representation
786+
if isinstance(data, unicode):
787+
return data.encode('utf-8')
788+
789+
# if this is a list of values, return list of string values
790+
if isinstance(data, list):
791+
return [get_strings(item) for item in data]
792+
793+
# if this is a dictionary, return dictionary of string keys and values
794+
if isinstance(data, dict):
795+
return {
796+
get_strings(key): get_strings(value)
797+
for key, value in data.iteritems()
798+
}
799+
# if it's anything else, return it in its original form
800+
return data
801+
802+
803+
def is_bika_installed():
804+
"""Check if Bika LIMS is installed in the Portal
805+
"""
806+
qi = api.portal.get_tool("portal_quickinstaller")
807+
return qi.isProductInstalled("bika.lims")

0 commit comments

Comments
 (0)