Passed
Push — master ( c1f984...7f6007 )
by Ramon
03:49
created

RejectSamplesView.__call__()   C

Complexity

Conditions 11

Size

Total Lines 66
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 40
dl 0
loc 66
rs 5.4
c 0
b 0
f 0
cc 11
nop 1

How to fix   Long Method    Complexity   

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:

Complexity

Complex classes like bika.lims.browser.analysisrequest.reject_samples.RejectSamplesView.__call__() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE.
4
#
5
# SENAITE.CORE is free software: you can redistribute it and/or modify it under
6
# the terms of the GNU General Public License as published by the Free Software
7
# Foundation, version 2.
8
#
9
# This program is distributed in the hope that it will be useful, but WITHOUT
10
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
# details.
13
#
14
# You should have received a copy of the GNU General Public License along with
15
# this program; if not, write to the Free Software Foundation, Inc., 51
16
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
#
18
# Copyright 2018-2020 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
22
from bika.lims import api
23
from bika.lims import bikaMessageFactory as _
24
from bika.lims import logger
25
from bika.lims import workflow as wf
26
from bika.lims.browser import BrowserView, ulocalized_time
27
from bika.lims.catalog.analysisrequest_catalog import \
28
    CATALOG_ANALYSIS_REQUEST_LISTING
29
from plone.memoize import view
30
31
from bika.lims.utils.analysisrequest import do_rejection
32
33
34
class RejectSamplesView(BrowserView):
35
    """View that renders the Samples rejection view
36
    """
37
    template = ViewPageTemplateFile("templates/reject_samples.pt")
38
39
    def __init__(self, context, request):
40
        super(RejectSamplesView, self).__init__(context, request)
41
        self.context = context
42
        self.request = request
43
        self.back_url = self.context.absolute_url()
44
45
    @property
46
    def is_notification_enabled(self):
47
        """Returns whether the notification on sample rejection is enabled
48
        """
49
        return api.get_setup().getNotifyOnSampleRejection()
50
51
    @property
52
    def is_rejection_workflow_enabled(self):
53
        """Return whether the rejection workflow is enabled
54
        """
55
        return api.get_setup().isRejectionWorkflowEnabled()
56
57
    def __call__(self):
58
        form = self.request.form
59
60
        # Form submit toggle
61
        form_submitted = form.get("submitted", False)
62
63
        # Buttons
64
        form_continue = form.get("button_continue", False)
65
        form_cancel = form.get("button_cancel", False)
66
67
        # Is Rejection Workflow Enabled
68
        if not self.is_rejection_workflow_enabled:
69
            return self.redirect(message=_("Rejection workflow is not enabled"),
70
                                 level="warning")
71
72
        # Get the objects from request
73
        samples = self.get_samples_from_request()
74
75
        # No Samples selected
76
        if not samples:
77
            return self.redirect(message=_("No items selected"),
78
                                 level="warning")
79
80
        # Handle rejection
81
        if form_submitted and form_continue:
82
            logger.info("*** REJECT SAMPLES ***")
83
            processed = []
84
            for sample in form.get("samples", []):
85
                sample_uid = sample.get("uid", "")
86
                reasons = sample.get("reasons", [])
87
                other = sample.get("other_reasons", "")
88
                if not sample_uid:
89
                    continue
90
91
                # Omit if no rejection reason specified
92
                if not any([reasons, other]):
93
                    continue
94
95
                # This is quite bizarre!
96
                # AR's Rejection reasons is a RecordsField, but with one
97
                # record only, that contains both predefined and other reasons.
98
                obj = api.get_object_by_uid(sample_uid)
99
                rejection_reasons = {
100
                    "other": other,
101
                    "selected": reasons
102
                }
103
                obj.setRejectionReasons([rejection_reasons])
104
105
                # Reject the sample
106
                notify = sample.get("notify", "") == "on"
107
                do_rejection(obj, notify=notify)
108
                processed.append(obj)
109
110
            if not processed:
111
                return self.redirect(message=_("No samples were rejected"))
112
113
            message = _("Rejected {} samples: {}").format(
114
                len(processed), ", ".join(map(api.get_id, processed)))
115
            return self.redirect(message=message)
116
117
        # Handle cancel
118
        if form_submitted and form_cancel:
119
            logger.info("*** CANCEL REJECTION ***")
120
            return self.redirect(message=_("Rejection cancelled"))
121
122
        return self.template()
123
124
    @view.memoize
125
    def get_samples_from_request(self):
126
        """Returns a list of objects coming from the "uids" request parameter
127
        """
128
        uids = self.request.form.get("uids", "")
129
        if isinstance(uids, basestring):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable basestring does not seem to be defined.
Loading history...
130
            uids = uids.split(",")
131
132
        uids = list(set(uids))
133
        if not uids:
134
            return []
135
136
        # Filter those analysis requests "reject" transition is allowed
137
        query = dict(portal_type="AnalysisRequest", UID=uids)
138
        brains = api.search(query, CATALOG_ANALYSIS_REQUEST_LISTING)
139
        samples = map(api.get_object, brains)
140
        return filter(lambda ob: wf.isTransitionAllowed(ob, "reject"), samples)
141
142
    @view.memoize
143
    def get_rejection_reasons(self):
144
        """Returns the list of available rejection reasons
145
        """
146
        return api.get_setup().getRejectionReasonsItems()
147
148
    def get_samples_data(self):
149
        """Returns a list of Samples data (dictionary)
150
        """
151
        for obj in self.get_samples_from_request():
152
            yield {
153
                "obj": obj,
154
                "id": api.get_id(obj),
155
                "uid": api.get_uid(obj),
156
                "title": api.get_title(obj),
157
                "path": api.get_path(obj),
158
                "url": api.get_url(obj),
159
                "sample_type": obj.getSampleTypeTitle(),
160
                "client_title": obj.getClientTitle(),
161
                "date": ulocalized_time(obj.created(), long_format=True),
162
            }
163
164
    def redirect(self, redirect_url=None, message=None, level="info"):
165
        """Redirect with a message
166
        """
167
        if redirect_url is None:
168
            redirect_url = self.back_url
169
        if message is not None:
170
            self.add_status_message(message, level)
171
        return self.request.response.redirect(redirect_url)
172
173
    def add_status_message(self, message, level="info"):
174
        """Set a portal status message
175
        """
176
        return self.context.plone_utils.addPortalMessage(message, level)
177