Passed
Push — master ( d8e2ec...90ae0b )
by Jordi
10:07 queued 04:19
created

DashboardView.get_analysisrequests_section()   B

Complexity

Conditions 3

Size

Total Lines 99
Code Lines 67

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 67
dl 0
loc 99
rs 8.08
c 0
b 0
f 0
cc 3
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE
4
#
5
# Copyright 2018 by it's authors.
6
# Some rights reserved. See LICENSE.rst, CONTRIBUTORS.rst.
7
8
import collections
9
import datetime
10
import json
11
from calendar import monthrange
12
from operator import itemgetter
13
from time import time
14
15
from DateTime import DateTime
16
from Products.Archetypes.public import DisplayList
17
from Products.CMFCore.utils import getToolByName
18
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
19
from bika.lims import bikaMessageFactory as _
20
from bika.lims import logger
21
from bika.lims.api import get_tool
22
from bika.lims.api import search
23
from bika.lims.browser import BrowserView
24
from bika.lims.catalog import CATALOG_ANALYSIS_LISTING
25
from bika.lims.catalog import CATALOG_ANALYSIS_REQUEST_LISTING
26
from bika.lims.catalog import CATALOG_WORKSHEET_LISTING
27
from bika.lims.utils import get_strings
28
from bika.lims.utils import get_unicode
29
from plone import api
30
from plone import protect
31
from plone.api.exc import InvalidParameterError
32
from plone.memoize import ram
33
from plone.memoize import view as viewcache
34
35
DASHBOARD_FILTER_COOKIE = 'dashboard_filter_cookie'
36
37
# Supported periodicities for evolution charts
38
PERIODICITY_DAILY = "d"
39
PERIODICITY_WEEKLY = "w"
40
PERIODICITY_MONTHLY = "m"
41
PERIODICITY_QUARTERLY = "q"
42
PERIODICITY_BIANNUAL = "b"
43
PERIODICITY_YEARLY = "y"
44
PERIODICITY_ALL = "a"
45
46
47
def get_dashboard_registry_record():
48
    """
49
    Return the 'bika.lims.dashboard_panels_visibility' values.
50
    :return: A dictionary or None
51
    """
52
    try:
53
        registry = api.portal.get_registry_record(
54
            'bika.lims.dashboard_panels_visibility')
55
        return registry
56
    except InvalidParameterError:
57
        # No entry in the registry for dashboard panels roles.
58
        # Maybe upgradestep 1.1.8 was not run?
59
        logger.warn("Cannot find a record with name "
60
                    "'bika.lims.dashboard_panels_visibility' in "
61
                    "registry_record. Missed upgrade 1.1.8?")
62
    return dict()
63
64
65
def set_dashboard_registry_record(registry_info):
66
    """
67
    Sets the 'bika.lims.dashboard_panels_visibility' values.
68
69
    :param registry_info: A dictionary type object with all its values as
70
    *unicode* objects.
71
    :return: A dictionary or None
72
    """
73
    try:
74
        api.portal.set_registry_record(
75
            'bika.lims.dashboard_panels_visibility', registry_info)
76
    except InvalidParameterError:
77
        # No entry in the registry for dashboard panels roles.
78
        # Maybe upgradestep 1.1.8 was not run?
79
        logger.warn("Cannot find a record with name "
80
                    "'bika.lims.dashboard_panels_visibility' in "
81
                    "registry_record. Missed upgrade 1.1.8?")
82
83
84
def setup_dashboard_panels_visibility_registry(section_name):
85
    """
86
    Initializes the values for panels visibility in registry_records. By
87
    default, only users with LabManager or Manager roles can see the panels.
88
    :param section_name:
89
    :return: An string like: "role1,yes,role2,no,rol3,no"
90
    """
91
    registry_info = get_dashboard_registry_record()
92
    role_permissions_list = []
93
    # Getting roles defined in the system
94
    roles = []
95
    acl_users = get_tool("acl_users")
96
    roles_tree = acl_users.portal_role_manager.listRoleIds()
97
    for role in roles_tree:
98
        roles.append(role)
99
    # Set view permissions to each role as 'yes':
100
    # "role1,yes,role2,no,rol3,no"
101
    for role in roles:
102
        role_permissions_list.append(role)
103
        visible = 'no'
104
        if role in ['LabManager', 'Manager']:
105
            visible = 'yes'
106
        role_permissions_list.append(visible)
107
    role_permissions = ','.join(role_permissions_list)
108
109
    # Set permissions string into dict
110
    registry_info[get_unicode(section_name)] = get_unicode(role_permissions)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable get_unicode does not seem to be defined.
Loading history...
111
    # Set new values to registry record
112
    set_dashboard_registry_record(registry_info)
113
    return registry_info
114
115
116
def get_dashboard_panels_visibility_by_section(section_name):
117
    """
118
    Return a list of pairs as values that represents the role-permission
119
    view relation for the panel section passed in.
120
    :param section_name: the panels section id.
121
    :return: a list of tuples.
122
    """
123
    registry_info = get_dashboard_registry_record()
124
    if section_name not in registry_info:
125
        # Registry hasn't been set, do it at least for this section
126
        registry_info = \
127
            setup_dashboard_panels_visibility_registry(section_name)
128
129
    pairs = registry_info.get(section_name)
130
    pairs = get_strings(pairs)
131
    if pairs is None:
132
        # In the registry, but with None value?
133
        setup_dashboard_panels_visibility_registry(section_name)
134
        return get_dashboard_panels_visibility_by_section(section_name)
135
136
    pairs = pairs.split(',')
137
    if len(pairs) == 0 or len(pairs) % 2 != 0:
138
        # Non-valid or malformed value
139
        setup_dashboard_panels_visibility_registry(section_name)
140
        return get_dashboard_panels_visibility_by_section(section_name)
141
142
    result = [
143
        (pairs[i], pairs[i + 1]) for i in range(len(pairs)) if i % 2 == 0]
144
    return result
145
146
147
def is_panel_visible_for_user(panel, user):
148
    """
149
    Checks if the user is allowed to see the panel
150
    :param panel: panel ID as string
151
    :param user: a MemberData object
152
    :return: Boolean
153
    """
154
    roles = user.getRoles()
155
    visibility = get_dashboard_panels_visibility_by_section(panel)
156
    for pair in visibility:
157
        if pair[0] in roles and pair[1] == 'yes':
158
            return True
159
    return False
160
161
162
class DashboardView(BrowserView):
163
    template = ViewPageTemplateFile("templates/dashboard.pt")
164
165
    def __init__(self, context, request):
166
        BrowserView.__init__(self, context, request)
167
        self.dashboard_cookie = None
168
        self.member = None
169
170
    def __call__(self):
171
        frontpage_url = self.portal_url + "/senaite-frontpage"
172
        if not self.context.bika_setup.getDashboardByDefault():
173
            # Do not render dashboard, render frontpage instead
174
            self.request.response.redirect(frontpage_url)
175
            return
176
177
        mtool = getToolByName(self.context, 'portal_membership')
178
        if mtool.isAnonymousUser():
179
            # Anonymous user, redirect to frontpage
180
            self.request.response.redirect(frontpage_url)
181
            return
182
183
        self.member = mtool.getAuthenticatedMember()
184
        self.periodicity = self.request.get('p', PERIODICITY_WEEKLY)
185
        self.dashboard_cookie = self.check_dashboard_cookie()
186
        date_range = self.get_date_range(self.periodicity)
187
        self.date_from = date_range[0]
188
        self.date_to = date_range[1]
189
190
        return self.template()
191
192
    def check_dashboard_cookie(self):
193
        """
194
        Check if the dashboard cookie should exist through bikasetup
195
        configuration.
196
197
        If it should exist but doesn't exist yet, the function creates it
198
        with all values as default.
199
        If it should exist and already exists, it returns the value.
200
        Otherwise, the function returns None.
201
202
        :return: a dictionary of strings
203
        """
204
        # Getting cookie
205
        cookie_raw = self.request.get(DASHBOARD_FILTER_COOKIE, None)
206
        # If it doesn't exist, create it with default values
207
        if cookie_raw is None:
208
            cookie_raw = self._create_raw_data()
209
            self.request.response.setCookie(
210
                DASHBOARD_FILTER_COOKIE,
211
                json.dumps(cookie_raw),
212
                quoted=False,
213
                path='/')
214
            return cookie_raw
215
        return get_strings(json.loads(cookie_raw))
216
217
    def is_filter_selected(self, selection_id, value):
218
        """
219
        Compares whether the 'selection_id' parameter value saved in the
220
        cookie is the same value as the "value" parameter.
221
222
        :param selection_id: a string as a dashboard_cookie key.
223
        :param value: The value to compare against the value from
224
        dashboard_cookie key.
225
        :return: Boolean.
226
        """
227
        selected = self.dashboard_cookie.get(selection_id)
228
        return selected == value
229
230
    def is_admin_user(self):
231
        """
232
        Checks if the user is the admin or a SiteAdmin user.
233
        :return: Boolean
234
        """
235
        user = api.user.get_current()
236
        roles = user.getRoles()
237
        return "LabManager" in roles or "Manager" in roles
238
239
    def _create_raw_data(self):
240
        """
241
        Gathers the different sections ids and creates a string as first
242
        cookie data.
243
244
        :return: A dictionary like:
245
            {'analyses':'all','analysisrequest':'all','worksheets':'all'}
246
        """
247
        result = {}
248
        for section in self.get_sections():
249
            result[section.get('id')] = 'all'
250
        return result
251
252
    def get_date_range(self, periodicity=PERIODICITY_WEEKLY):
253
        """Returns a date range (date from, date to) that suits with the passed
254
        in periodicity.
255
256
        :param periodicity: string that represents the periodicity
257
        :type periodicity: str
258
        :return: A date range
259
        :rtype: [(DateTime, DateTime)]
260
        """
261
        today = datetime.date.today()
262
        if periodicity == PERIODICITY_DAILY:
263
            # Daily, load last 30 days
264
            date_from = DateTime() - 30
265
            date_to = DateTime() + 1
266
            return date_from, date_to
267
268
        if periodicity == PERIODICITY_MONTHLY:
269
            # Monthly, load last 2 years
270
            min_year = today.year - 1 if today.month == 12 else today.year - 2
271
            min_month = 1 if today.month == 12 else today.month
272
            date_from = DateTime(min_year, min_month, 1)
273
            date_to = DateTime(today.year, today.month,
274
                               monthrange(today.year, today.month)[1],
275
                               23, 59, 59)
276
            return date_from, date_to
277
278
        if periodicity == PERIODICITY_QUARTERLY:
279
            # Quarterly, load last 4 years
280
            m = (((today.month - 1) / 3) * 3) + 1
281
            min_year = today.year - 4 if today.month == 12 else today.year - 5
282
            date_from = DateTime(min_year, m, 1)
283
            date_to = DateTime(today.year, m + 2,
284
                               monthrange(today.year, m + 2)[1], 23, 59,
285
                               59)
286
            return date_from, date_to
287
        if periodicity == PERIODICITY_BIANNUAL:
288
            # Biannual, load last 10 years
289
            m = (((today.month - 1) / 6) * 6) + 1
290
            min_year = today.year - 10 if today.month == 12 else today.year - 11
291
            date_from = DateTime(min_year, m, 1)
292
            date_to = DateTime(today.year, m + 5,
293
                               monthrange(today.year, m + 5)[1], 23, 59,
294
                               59)
295
            return date_from, date_to
296
297
        if periodicity in [PERIODICITY_YEARLY, PERIODICITY_ALL]:
298
            # Yearly or All time, load last 15 years
299
            min_year = today.year - 15 if today.month == 12 else today.year - 16
300
            date_from = DateTime(min_year, 1, 1)
301
            date_to = DateTime(today.year, 12, 31, 23, 59, 59)
302
            return date_from, date_to
303
304
        # Default Weekly, load last six months
305
        year, weeknum, dow = today.isocalendar()
306
        min_year = today.year if today.month > 6 else today.year - 1
307
        min_month = today.month - 6 if today.month > 6 \
308
            else (today.month - 6) + 12
309
        date_from = DateTime(min_year, min_month, 1)
310
        date_to = DateTime() - dow + 7
311
        return date_from, date_to
312
313
    def get_sections(self):
314
        """ Returns an array with the sections to be displayed.
315
            Every section is a dictionary with the following structure:
316
                {'id': <section_identifier>,
317
                 'title': <section_title>,
318
                'panels': <array of panels>}
319
        """
320
        sections = []
321
        user = api.user.get_current()
322
        if is_panel_visible_for_user('analyses', user):
323
            sections.append(self.get_analyses_section())
324
        if is_panel_visible_for_user('analysisrequests', user):
325
            sections.append(self.get_analysisrequests_section())
326
        if is_panel_visible_for_user('worksheets', user):
327
            sections.append(self.get_worksheets_section())
328
        return sections
329
330
    def get_filter_options(self):
331
        """
332
        Returns dasboard filter options.
333
        :return: Boolean
334
        """
335
        dash_opt = DisplayList((
336
            ('all', _('All')),
337
            ('mine', _('Mine')),
338
        ))
339
        return dash_opt
340
341
    def _getStatistics(self, name, description, url, catalog, criterias, total):
342
        out = {'type':        'simple-panel',
343
               'name':        name,
344
               'class':       'informative',
345
               'description': description,
346
               'total':       total,
347
               'link':        self.portal_url + '/' + url}
348
349
        results = 0
350
        ratio = 0
351
        if total > 0:
352
            results = self.search_count(criterias, catalog.id)
353
            results = results if total >= results else total
354
            ratio = (float(results)/float(total))*100 if results > 0 else 0
355
        ratio = str("%%.%sf" % 1) % ratio
356
        out['legend'] = _('of') + " " + str(total) + ' (' + ratio + '%)'
357
        out['number'] = results
358
        out['percentage'] = float(ratio)
359
        return out
360
361
    def get_analysisrequests_section(self):
362
        """ Returns the section dictionary related with Analysis
363
            Requests, that contains some informative panels (like
364
            ARs to be verified, ARs to be published, etc.)
365
        """
366
        out = []
367
        catalog = getToolByName(self.context, CATALOG_ANALYSIS_REQUEST_LISTING)
368
        query = {'portal_type': "AnalysisRequest",
369
                 'is_active': True}
370
371
        # Check if dashboard_cookie contains any values to query
372
        # elements by
373
        query = self._update_criteria_with_filters(query, 'analysisrequests')
374
375
        # Active Samples (All)
376
        total = self.search_count(query, catalog.id)
377
378
        # Sampling workflow enabled?
379
        if self.context.bika_setup.getSamplingWorkflowEnabled():
380
            # Samples awaiting to be sampled or scheduled
381
            name = _('Samples to be sampled')
382
            desc = _("To be sampled")
383
            purl = 'samples?samples_review_state=to_be_sampled'
384
            query['review_state'] = ['to_be_sampled', ]
385
            out.append(self._getStatistics(name, desc, purl, catalog, query, total))
386
387
            # Samples awaiting to be preserved
388
            name = _('Samples to be preserved')
389
            desc = _("To be preserved")
390
            purl = 'samples?samples_review_state=to_be_preserved'
391
            query['review_state'] = ['to_be_preserved', ]
392
            out.append(self._getStatistics(name, desc, purl, catalog, query, total))
393
394
            # Samples scheduled for Sampling
395
            name = _('Samples scheduled for sampling')
396
            desc = _("Sampling scheduled")
397
            purl = 'samples?samples_review_state=scheduled_sampling'
398
            query['review_state'] = ['scheduled_sampling', ]
399
            out.append(self._getStatistics(name, desc, purl, catalog, query, total))
400
401
        # Samples awaiting for reception
402
        name = _('Samples to be received')
403
        desc = _("Reception pending")
404
        purl = 'analysisrequests?analysisrequests_review_state=sample_due'
405
        query['review_state'] = ['sample_due', ]
406
        out.append(self._getStatistics(name, desc, purl, catalog, query, total))
407
408
        # Samples under way
409
        name = _('Samples with results pending')
410
        desc = _("Results pending")
411
        purl = 'analysisrequests?analysisrequests_review_state=sample_received'
412
        query['review_state'] = ['attachment_due',
413
                                 'sample_received', ]
414
        out.append(self._getStatistics(name, desc, purl, catalog, query, total))
415
416
        # Samples to be verified
417
        name = _('Samples to be verified')
418
        desc = _("To be verified")
419
        purl = 'analysisrequests?analysisrequests_review_state=to_be_verified'
420
        query['review_state'] = ['to_be_verified', ]
421
        out.append(self._getStatistics(name, desc, purl, catalog, query, total))
422
423
        # Samples verified (to be published)
424
        name = _('Samples verified')
425
        desc = _("Verified")
426
        purl = 'analysisrequests?analysisrequests_review_state=verified'
427
        query['review_state'] = ['verified', ]
428
        out.append(self._getStatistics(name, desc, purl, catalog, query, total))
429
430
        # Samples published
431
        name = _('Samples published')
432
        desc = _("Published")
433
        purl = 'analysisrequests?analysisrequests_review_state=published'
434
        query['review_state'] = ['published', ]
435
        out.append(self._getStatistics(name, desc, purl, catalog, query, total))
436
437
        # Samples to be printed
438
        if self.context.bika_setup.getPrintingWorkflowEnabled():
439
            name = _('Samples to be printed')
440
            desc = _("To be printed")
441
            purl = 'analysisrequests?analysisrequests_getPrinted=0'
442
            query['getPrinted'] = '0'
443
            query['review_state'] = ['published', ]
444
            out.append(
445
                self._getStatistics(name, desc, purl, catalog, query, total))
446
447
        # Chart with the evolution of ARs over a period, grouped by
448
        # periodicity
449
        outevo = self.fill_dates_evo(catalog, query)
450
        out.append({'type':         'bar-chart-panel',
451
                    'name':         _('Evolution of Samples'),
452
                    'class':        'informative',
453
                    'description':  _('Evolution of Samples'),
454
                    'data':         json.dumps(outevo),
455
                    'datacolors':   json.dumps(self.get_colors_palette())})
456
457
        return {'id': 'analysisrequests',
458
                'title': _('Samples'),
459
                'panels': out}
460
461
    def get_worksheets_section(self):
462
        """ Returns the section dictionary related with Worksheets,
463
            that contains some informative panels (like
464
            WS to be verified, WS with results pending, etc.)
465
        """
466
        out = []
467
        bc = getToolByName(self.context, CATALOG_WORKSHEET_LISTING)
468
        query = {'portal_type': "Worksheet", }
469
470
        # Check if dashboard_cookie contains any values to query
471
        # elements by
472
        query = self._update_criteria_with_filters(query, 'worksheets')
473
474
        # Active Worksheets (all)
475
        total = self.search_count(query, bc.id)
476
477
        # Open worksheets
478
        name = _('Results pending')
479
        desc = _('Results pending')
480
        purl = 'worksheets?list_review_state=open'
481
        query['review_state'] = ['open', 'attachment_due']
482
        out.append(self._getStatistics(name, desc, purl, bc, query, total))
483
484
        # Worksheets to be verified
485
        name = _('To be verified')
486
        desc = _('To be verified')
487
        purl = 'worksheets?list_review_state=to_be_verified'
488
        query['review_state'] = ['to_be_verified', ]
489
        out.append(self._getStatistics(name, desc, purl, bc, query, total))
490
491
        # Worksheets verified
492
        name = _('Verified')
493
        desc = _('Verified')
494
        purl = 'worksheets?list_review_state=verified'
495
        query['review_state'] = ['verified', ]
496
        out.append(self._getStatistics(name, desc, purl, bc, query, total))
497
498
        # Chart with the evolution of WSs over a period, grouped by
499
        # periodicity
500
        outevo = self.fill_dates_evo(bc, query)
501
        out.append({'type':         'bar-chart-panel',
502
                    'name':         _('Evolution of Worksheets'),
503
                    'class':        'informative',
504
                    'description':  _('Evolution of Worksheets'),
505
                    'data':         json.dumps(outevo),
506
                    'datacolors':   json.dumps(self.get_colors_palette())})
507
508
        return {'id': 'worksheets',
509
                'title': _('Worksheets'),
510
                'panels': out}
511
512
    def get_analyses_section(self):
513
        """ Returns the section dictionary related with Analyses,
514
            that contains some informative panels (analyses pending
515
            analyses assigned, etc.)
516
        """
517
        out = []
518
        bc = getToolByName(self.context, CATALOG_ANALYSIS_LISTING)
519
        query = {'portal_type': "Analysis", 'is_active': True}
520
521
        # Check if dashboard_cookie contains any values to query elements by
522
        query = self._update_criteria_with_filters(query, 'analyses')
523
524
        # Active Analyses (All)
525
        total = self.search_count(query, bc.id)
526
527
        # Analyses to be assigned
528
        name = _('Assignment pending')
529
        desc = _('Assignment pending')
530
        purl = '#'
531
        query['review_state'] = ['unassigned']
532
        out.append(self._getStatistics(name, desc, purl, bc, query, total))
533
534
        # Analyses pending
535
        name = _('Results pending')
536
        desc = _('Results pending')
537
        purl = '#'
538
        query['review_state'] = ['unassigned', 'assigned', ]
539
        out.append(self._getStatistics(name, desc, purl, bc, query, total))
540
541
        # Analyses to be verified
542
        name = _('To be verified')
543
        desc = _('To be verified')
544
        purl = '#'
545
        query['review_state'] = ['to_be_verified', ]
546
        out.append(self._getStatistics(name, desc, purl, bc, query, total))
547
548
        # Analyses verified
549
        name = _('Verified')
550
        desc = _('Verified')
551
        purl = '#'
552
        query['review_state'] = ['verified', ]
553
        out.append(self._getStatistics(name, desc, purl, bc, query, total))
554
555
        # Chart with the evolution of Analyses over a period, grouped by
556
        # periodicity
557
        outevo = self.fill_dates_evo(bc, query)
558
        out.append({'type':         'bar-chart-panel',
559
                    'name':         _('Evolution of Analyses'),
560
                    'class':        'informative',
561
                    'description':  _('Evolution of Analyses'),
562
                    'data':         json.dumps(outevo),
563
                    'datacolors':   json.dumps(self.get_colors_palette())})
564
        return {'id': 'analyses',
565
                'title': _('Analyses'),
566
                'panels': out}
567
568
    def get_states_map(self, portal_type):
569
        if portal_type == 'Analysis':
570
            return {'unassigned':      _('Assignment pending'),
571
                    'assigned':        _('Results pending'),
572
                    'to_be_verified':  _('To be verified'),
573
                    'rejected':        _('Rejected'),
574
                    'retracted':       _('Retracted'),
575
                    'verified':        _('Verified'),
576
                    'published':       _('Published')}
577
        elif portal_type == 'AnalysisRequest':
578
            return {'to_be_sampled':       _('To be sampled'),
579
                    'to_be_preserved':     _('To be preserved'),
580
                    'scheduled_sampling':  _('Sampling scheduled'),
581
                    'sample_due':          _('Reception pending'),
582
                    'rejected':            _('Rejected'),
583
                    'invalid':             _('Invalid'),
584
                    'sample_received':     _('Results pending'),
585
                    'assigned':            _('Results pending'),
586
                    'attachment_due':      _('Results pending'),
587
                    'to_be_verified':      _('To be verified'),
588
                    'verified':            _('Verified'),
589
                    'published':           _('Published')}
590
        elif portal_type == 'Worksheet':
591
            return {'open':            _('Results pending'),
592
                    'attachment_due':  _('Results pending'),
593
                    'to_be_verified':  _('To be verified'),
594
                    'verified':        _('Verified')}
595
596
    def get_colors_palette(self):
597
        return {
598
            'to_be_sampled':                '#917A4C',
599
            _('To be sampled'):             '#917A4C',
600
601
            'to_be_preserved':              '#C2803E',
602
            _('To be preserved'):           '#C2803E',
603
604
            'scheduled_sampling':           '#F38630',
605
            _('Sampling scheduled'):        '#F38630',
606
607
            'sample_due':                   '#FA6900',
608
            _('Reception pending'):         '#FA6900',
609
610
            'sample_received':              '#E0E4CC',
611
            _('Assignment pending'):        '#E0E4CC',
612
            _('Sample received'):           '#E0E4CC',
613
614
            'assigned':                     '#dcdcdc',
615
            'attachment_due':               '#dcdcdc',
616
            'open':                         '#dcdcdc',
617
            _('Results pending'):           '#dcdcdc',
618
619
            'rejected':                     '#FF6B6B',
620
            'retracted':                    '#FF6B6B',
621
            _('Rejected'):                  '#FF6B6B',
622
            _('Retracted'):                 '#FF6B6B',
623
624
            'invalid':                      '#C44D58',
625
            _('Invalid'):                   '#C44D58',
626
627
            'to_be_verified':               '#A7DBD8',
628
            _('To be verified'):            '#A7DBD8',
629
630
            'verified':                     '#69D2E7',
631
            _('Verified'):                  '#69D2E7',
632
633
            'published':                    '#83AF9B',
634
            _('Published'):                 '#83AF9B',
635
        }
636
637
    def _getDateStr(self, period, created):
638
        if period == PERIODICITY_YEARLY:
639
            created = created.year()
640
        elif period == PERIODICITY_BIANNUAL:
641
            m = (((created.month()-1)/6)*6)+1
642
            created = '%s-%s' % (str(created.year())[2:], str(m).zfill(2))
643
        elif period == PERIODICITY_QUARTERLY:
644
            m = (((created.month()-1)/3)*3)+1
645
            created = '%s-%s' % (str(created.year())[2:], str(m).zfill(2))
646
        elif period == PERIODICITY_MONTHLY:
647
            created = '%s-%s' % (str(created.year())[2:], str(created.month()).zfill(2))
648
        elif period == PERIODICITY_WEEKLY:
649
            d = (((created.day()-1)/7)*7)+1
650
            year, weeknum, dow = created.asdatetime().isocalendar()
651
            created = created - dow
652
            created = '%s-%s-%s' % (str(created.year())[2:], str(created.month()).zfill(2), str(created.day()).zfill(2))
653
        elif period == PERIODICITY_ALL:
654
            # All time, but evolution chart grouped by year
655
            created = created.year()
656
        else:
657
            created = '%s-%s-%s' % (str(created.year())[2:], str(created.month()).zfill(2), str(created.day()).zfill(2))
658
        return created
659
660
    def fill_dates_evo(self, catalog, query):
661
        sorted_query = collections.OrderedDict(sorted(query.items()))
662
        query_json = json.dumps(sorted_query)
663
        return self._fill_dates_evo(query_json, catalog.id, self.periodicity)
664
665
    def _fill_dates_evo_cachekey(method, self, query_json, catalog_name,
666
                                 periodicity):
667
        hour = time() // (60 * 60 * 2)
668
        return hour, catalog_name, query_json, periodicity
669
670
    @ram.cache(_fill_dates_evo_cachekey)
671
    def _fill_dates_evo(self, query_json, catalog_name, periodicity):
672
        """Returns an array of dictionaries, where each dictionary contains the
673
        amount of items created at a given date and grouped by review_state,
674
        based on the passed in periodicity.
675
676
        This is an expensive function that will not be called more than once
677
        every 2 hours (note cache decorator with `time() // (60 * 60 * 2)
678
        """
679
        outevoidx = {}
680
        outevo = []
681
        days = 1
682
        if periodicity == PERIODICITY_YEARLY:
683
            days = 336
684
        elif periodicity == PERIODICITY_BIANNUAL:
685
            days = 168
686
        elif periodicity == PERIODICITY_QUARTERLY:
687
            days = 84
688
        elif periodicity == PERIODICITY_MONTHLY:
689
            days = 28
690
        elif periodicity == PERIODICITY_WEEKLY:
691
            days = 7
692
        elif periodicity == PERIODICITY_ALL:
693
            days = 336
694
695
        # Get the date range
696
        date_from, date_to = self.get_date_range(periodicity)
697
        query = json.loads(query_json)
698
        if 'review_state' in query:
699
            del query['review_state']
700
        query['sort_on'] = 'created'
701
        query['created'] = {'query': (date_from, date_to),
702
                            'range': 'min:max'}
703
704
        otherstate = _('Other status')
705
        statesmap = self.get_states_map(query['portal_type'])
706
        stats = statesmap.values()
707
        stats.sort()
708
        stats.append(otherstate)
709
        statscount = {s: 0 for s in stats}
710
        # Add first all periods, cause we want all segments to be displayed
711
        curr = date_from.asdatetime()
712
        end = date_to.asdatetime()
713
        while curr < end:
714
            currstr = self._getDateStr(periodicity, DateTime(curr))
715
            if currstr not in outevoidx:
716
                outdict = {'date': currstr}
717
                for k in stats:
718
                    outdict[k] = 0
719
                outevo.append(outdict)
720
                outevoidx[currstr] = len(outevo)-1
721
            curr = curr + datetime.timedelta(days=days)
722
723
        brains = search(query, catalog_name)
724
        for brain in brains:
725
            created = brain.created
726
            state = brain.review_state
727
            if state not in statesmap:
728
                logger.warn("'%s' State for '%s' not available" % (state, query['portal_type']))
729
            state = statesmap[state] if state in statesmap else otherstate
730
            created = self._getDateStr(periodicity, created)
731
            statscount[state] += 1
732
            if created in outevoidx:
733
                oidx = outevoidx[created]
734
                if state in outevo[oidx]:
735
                    outevo[oidx][state] += 1
736
                else:
737
                    outevo[oidx][state] = 1
738
            else:
739
                # Create new row
740
                currow = {'date': created,
741
                          state: 1}
742
                outevo.append(currow)
743
744
        # Remove all those states for which there is no data
745
        rstates = [k for k, v in statscount.items() if v == 0]
746
        for o in outevo:
747
            for r in rstates:
748
                if r in o:
749
                    del o[r]
750
751
        # Sort available status by number of occurences descending
752
        sorted_states = sorted(statscount.items(), key=itemgetter(1))
753
        sorted_states = map(lambda item: item[0], sorted_states)
754
        sorted_states.reverse()
755
        return {'data': outevo, 'states': sorted_states}
756
757
    def search_count(self, query, catalog_name):
758
        sorted_query = collections.OrderedDict(sorted(query.items()))
759
        query_json = json.dumps(sorted_query)
760
        return self._search_count(query_json, catalog_name)
761
762
    @viewcache.memoize
763
    def _search_count(self, query_json, catalog_name):
764
        query = json.loads(query_json)
765
        brains = search(query, catalog_name)
766
        return len(brains)
767
768
    def _update_criteria_with_filters(self, query, section_name):
769
        """
770
        This method updates the 'query' dictionary with the criteria stored in
771
        dashboard cookie.
772
773
        :param query: A dictionary with search criteria.
774
        :param section_name: The dashboard section name
775
        :return: The 'query' dictionary
776
        """
777
        if self.dashboard_cookie is None:
778
            return query
779
        cookie_criteria = self.dashboard_cookie.get(section_name)
780
        if cookie_criteria == 'mine':
781
            query['Creator'] = self.member.getId()
782
        return query
783
784
    def get_dashboard_panels_visibility(self, section_name):
785
        """
786
        Return a list of pairs as values that represents the role-permission
787
        view relation for the panel section.
788
        :param section_name: the panels section id.
789
        :return: a list of tuples.
790
        """
791
        return get_dashboard_panels_visibility_by_section(section_name)
792
793
794
class DashboardViewPermissionUpdate(BrowserView):
795
    """
796
    Updates the values in 'bika.lims.dashboard_panels_visibility' registry.
797
    """
798
799
    def __call__(self):
800
        protect.CheckAuthenticator(self.request)
801
        # Getting values from post
802
        section_name = self.request.get('section_name', None)
803
        if section_name is None:
804
            return None
805
        role_id = self.request.get('role_id', None)
806
        if role_id is None:
807
            return None
808
        check_state = self.request.get('check_state', None)
809
        if check_state is None:
810
            return None
811
        elif check_state == 'false':
812
            check_state = 'no'
813
        else:
814
            check_state = 'yes'
815
        # Update registry
816
        registry_info = get_dashboard_registry_record()
817
        pairs = get_dashboard_panels_visibility_by_section(section_name)
818
        role_permissions = list()
819
        for pair in pairs:
820
            visibility = pair[1]
821
            if pair[0] == role_id:
822
                visibility = check_state
823
            value = '{0},{1}'.format(pair[0], visibility)
824
            role_permissions.append(value)
825
        role_permissions = ','.join(role_permissions)
826
        # Set permissions string into dict
827
        registry_info[section_name] = get_unicode(role_permissions)
828
        set_dashboard_registry_record(registry_info)
829
        return True
830