Passed
Push — master ( c72550...021814 )
by Ramon
05:39
created

build.bika.lims.browser.analysisrequest.reject_samples   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 163
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 26
eloc 100
dl 0
loc 163
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A RejectSamplesView.__init__() 0 5 1
A RejectSamplesView.redirect() 0 8 3
A RejectSamplesView.get_rejection_reasons() 0 5 1
A RejectSamplesView.add_status_message() 0 4 1
A RejectSamplesView.is_rejection_workflow_enabled() 0 5 1
A RejectSamplesView.get_samples_data() 0 14 2
A RejectSamplesView.get_samples_from_request() 0 17 4
A RejectSamplesView.is_notification_enabled() 0 5 1
D RejectSamplesView.__call__() 0 66 12
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE
4
#
5
# Copyright 2019 by it's authors.
6
# Some rights reserved. See LICENSE.rst, CONTRIBUTORS.rst.
7
8
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
9
from bika.lims import api
10
from bika.lims import bikaMessageFactory as _
11
from bika.lims import logger
12
from bika.lims import workflow as wf
13
from bika.lims.browser import BrowserView, ulocalized_time
14
from bika.lims.catalog.analysisrequest_catalog import \
15
    CATALOG_ANALYSIS_REQUEST_LISTING
16
from bika.lims.utils.analysisrequest import notify_rejection
17
from plone.memoize import view
18
19
20
class RejectSamplesView(BrowserView):
21
    """View that renders the Samples rejection view
22
    """
23
    template = ViewPageTemplateFile("templates/reject_samples.pt")
24
25
    def __init__(self, context, request):
26
        super(RejectSamplesView, self).__init__(context, request)
27
        self.context = context
28
        self.request = request
29
        self.back_url = self.context.absolute_url()
30
31
    @property
32
    def is_notification_enabled(self):
33
        """Returns whether the notification on sample rejection is enabled
34
        """
35
        return api.get_setup().getNotifyOnSampleRejection()
36
37
    @property
38
    def is_rejection_workflow_enabled(self):
39
        """Return whether the rejection workflow is enabled
40
        """
41
        return api.get_setup().isRejectionWorkflowEnabled()
42
43
    def __call__(self):
44
        form = self.request.form
45
46
        # Form submit toggle
47
        form_submitted = form.get("submitted", False)
48
49
        # Buttons
50
        form_continue = form.get("button_continue", False)
51
        form_cancel = form.get("button_cancel", False)
52
53
        # Is Rejection Workflow Enabled
54
        if not self.is_rejection_workflow_enabled:
55
            return self.redirect(message=_("Rejection workflow is not enabled"),
56
                                 level="warning")
57
58
        # Get the objects from request
59
        samples = self.get_samples_from_request()
60
61
        # No Samples selected
62
        if not samples:
63
            return self.redirect(message=_("No items selected"),
64
                                 level="warning")
65
66
        # Handle rejection
67
        if form_submitted and form_continue:
68
            logger.info("*** REJECT SAMPLES ***")
69
            processed = []
70
            for sample in form.get("samples", []):
71
                sample_uid = sample.get("uid", "")
72
                reasons = sample.get("reasons", [])
73
                other = sample.get("other_reasons", "")
74
                if not sample_uid:
75
                    continue
76
77
                # Omit if no rejection reason specified
78
                if not any([reasons, other]):
79
                    continue
80
81
                # This is quite bizarre!
82
                # AR's Rejection reasons is a RecordsField, but with one
83
                # record only, that contains both predefined and other reasons.
84
                obj = api.get_object_by_uid(sample_uid)
85
                rejection_reasons = {
86
                    "other": other,
87
                    "selected": reasons }
88
                obj.setRejectionReasons([rejection_reasons])
89
                wf.doActionFor(obj, "reject")
90
                processed.append(obj)
91
92
                # Client needs to be notified?
93
                if sample.get("notify", "") == "on":
94
                    notify_rejection(obj)
95
96
            if not processed:
97
                return self.redirect(message=_("No samples were rejected"))
98
99
            message = _("Rejected {} samples: {}").format(
100
                len(processed), ", ".join(map(api.get_id, processed)))
101
            return self.redirect(message=message)
102
103
        # Handle cancel
104
        if form_submitted and form_cancel:
105
            logger.info("*** CANCEL REJECTION ***")
106
            return self.redirect(message=_("Rejection cancelled"))
107
108
        return self.template()
109
110
    @view.memoize
111
    def get_samples_from_request(self):
112
        """Returns a list of objects coming from the "uids" request parameter
113
        """
114
        uids = self.request.form.get("uids", "")
115
        if isinstance(uids, basestring):
116
            uids = uids.split(",")
117
118
        uids = list(set(uids))
119
        if not uids:
120
            return []
121
122
        # Filter those analysis requests "reject" transition is allowed
123
        query = dict(portal_type="AnalysisRequest", UID=uids)
124
        brains = api.search(query, CATALOG_ANALYSIS_REQUEST_LISTING)
125
        samples = map(api.get_object, brains)
126
        return filter(lambda ob: wf.isTransitionAllowed(ob, "reject"), samples)
127
128
    @view.memoize
129
    def get_rejection_reasons(self):
130
        """Returns the list of available rejection reasons
131
        """
132
        return api.get_setup().getRejectionReasonsItems()
133
134
    def get_samples_data(self):
135
        """Returns a list of Samples data (dictionary)
136
        """
137
        for obj in self.get_samples_from_request():
138
            yield {
139
                "obj": obj,
140
                "id": api.get_id(obj),
141
                "uid": api.get_uid(obj),
142
                "title": api.get_title(obj),
143
                "path": api.get_path(obj),
144
                "url": api.get_url(obj),
145
                "sample_type": obj.getSampleTypeTitle(),
146
                "client_title": obj.getClientTitle(),
147
                "date": ulocalized_time(obj.created(), long_format=True),
148
            }
149
150
    def redirect(self, redirect_url=None, message=None, level="info"):
151
        """Redirect with a message
152
        """
153
        if redirect_url is None:
154
            redirect_url = self.back_url
155
        if message is not None:
156
            self.add_status_message(message, level)
157
        return self.request.response.redirect(redirect_url)
158
159
    def add_status_message(self, message, level="info"):
160
        """Set a portal status message
161
        """
162
        return self.context.plone_utils.addPortalMessage(message, level)
163