BatchBookView.folderitems()   F
last analyzed

Complexity

Conditions 24

Size

Total Lines 119
Code Lines 90

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 90
dl 0
loc 119
rs 0
c 0
b 0
f 0
cc 24
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.batch.batchbook.BatchBookView.folderitems() 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-2025 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
import re
22
from collections import OrderedDict
23
from collections import defaultdict
24
from operator import itemgetter
25
26
from bika.lims import api
27
from bika.lims.utils import get_link_for
28
from bika.lims import bikaMessageFactory as _
29
from bika.lims.api.security import check_permission
30
from bika.lims.browser.bika_listing import BikaListingView
31
from bika.lims.interfaces import IBatchBookView
32
from senaite.core.permissions import AddAnalysisRequest
33
from senaite.core.permissions import FieldEditAnalysisResult
34
from senaite.core.i18n import translate as t
35
from Products.CMFCore.permissions import ModifyPortalContent
36
from zope.interface import implementer
37
38
DESCRIPTION = _("The batch book allows to introduce analysis results for all "
39
                "samples in this batch. Please note that submitting the "
40
                "results for verification is only possible within samples or "
41
                "worksheets, because additional information like e.g. the "
42
                "instrument or the method need to be set additionally.")
43
44
45
@implementer(IBatchBookView)
46
class BatchBookView(BikaListingView):
47
48
    def __init__(self, context, request):
49
        super(BatchBookView, self).__init__(context, request)
50
51
        self.context_actions = {}
52
        self.contentFilter = {"sort_on": "created"}
53
        self.title = context.Title()
54
        self.description = t(DESCRIPTION)
55
56
        self.show_select_column = True
57
        self.show_search = False
58
        self.pagesize = 999999
59
        self.form_id = "batchbook"
60
        self.show_categories = True
61
        self.expand_all_categories = True
62
        self.show_column_toggles = False
63
64
        self.icon = "{}{}".format(
65
            self.portal_url, "/senaite_theme/icon/batchbook")
66
67
        self.columns = OrderedDict((
68
            ("AnalysisRequest", {
69
                "title": _("Sample"),
70
                "sortable": False,
71
            }),
72
            ("SampleType", {
73
                "title": _("Sample Type"),
74
                "sortable": False,
75
            }),
76
            ("SamplePoint", {
77
                "title": _("Sample Point"),
78
                "sortable": False,
79
            }),
80
            ("ClientOrderNumber", {
81
                "title": _("Client Order Number"),
82
                "sortable": False,
83
            }),
84
            ("created", {
85
                "title": _("Date Registered"),
86
                "sortable": False,
87
            }),
88
            ("state_title", {
89
                "title": _("State"),
90
                "index": "review_state",
91
                "sortable": False,
92
            }),
93
        ))
94
95
        self.review_states = [
96
            {
97
                "id": "default",
98
                "title": _("All"),
99
                "contentFilter": {},
100
                "columns": self.columns.keys()
101
            },
102
        ]
103
104
    def is_copy_to_new_allowed(self):
105
        """Checks if it is allowed to copy the sample
106
        """
107
        if check_permission(AddAnalysisRequest, self.context):
108
            return True
109
        return False
110
111
    def can_edit_analysis_result(self, analysis):
112
        """Checks if the current user can edit the analysis result
113
        """
114
        return check_permission(FieldEditAnalysisResult, analysis)
115
116
    def update(self):
117
        """Update hook
118
        """
119
        super(BatchBookView, self).update()
120
        # check if the use can modify
121
        self.allow_edit = check_permission(ModifyPortalContent, self.context)
122
        # append copy to new transition
123
        if self.is_copy_to_new_allowed():
124
            self.add_copy_transition()
125
126
    def add_copy_transition(self):
127
        """Add copy transtion
128
        """
129
        review_states = []
130
        for review_state in self.review_states:
131
            custom_transitions = review_state.get("custom_transitions", [])
132
            base_url = api.get_url(self.context)
133
            custom_transitions.append({
134
                "id": "copy_to_new",
135
                "title": _("Copy to new"),
136
                "url": "{}/workflow_action?action=copy_to_new".format(base_url)
137
            })
138
            review_state["custom_transitions"] = custom_transitions
139
            review_states.append(review_state)
140
        self.review_states = review_states
141
142
    def before_render(self):
143
        """Before render hook
144
        """
145
        super(BatchBookView, self).before_render()
146
147
    def folderitems(self):
148
        """Accumulate a list of all AnalysisRequest objects contained in
149
        this Batch, as well as those which are inherited.
150
        """
151
        ars = self.context.getAnalysisRequests(is_active=True)
152
        self.total = len(ars)
153
154
        self.categories = []
155
        analyses = defaultdict(list)
156
        items = []
157
        distinct = []  # distinct analyses (each one a different service)
158
        keywords = []
159
        for ar in ars:
160
            for analysis in ar.getAnalyses(full_objects=True):
161
                analyses[ar.getId()].append(analysis)
162
                if analysis.getKeyword() not in keywords:
163
                    # we use a keyword check, because versioned services are !=.
164
                    keywords.append(analysis.getKeyword())
165
                    distinct.append(analysis)
166
167
            batchlink = ""
168
            batch = ar.getBatch()
169
            if batch:
170
                batchlink = get_link_for(batch)
171
172
            arlink = get_link_for(ar)
173
174
            subgroup = ar.getSubGroup()
175
            sub_title = subgroup.Title() if subgroup else t(_("No Subgroup"))
176
            sub_sort = subgroup.getSortKey() if subgroup else "1"
177
            sub_class = re.sub(r"[^A-Za-z\w\d\-\_]", "", sub_title)
178
179
            if [sub_sort, sub_title] not in self.categories:
180
                self.categories.append([sub_sort, sub_title])
181
182
            wf = api.get_tool("portal_workflow")
183
            review_state = api.get_review_status(ar)
184
            state_title = wf.getTitleForStateOnType(
185
                review_state, "AnalysisRequest")
186
187
            item = {
188
                "obj": ar,
189
                "id": ar.id,
190
                "uid": ar.UID(),
191
                "category": sub_title,
192
                "title": ar.Title(),
193
                "type_class": "contenttype-AnalysisRequest",
194
                "url": ar.absolute_url(),
195
                "relative_url": ar.absolute_url(),
196
                "view_url": ar.absolute_url(),
197
                "created": self.ulocalized_time(ar.created(), long_format=1),
198
                "sort_key": ar.created(),
199
                "replace": {
200
                    "Batch": batchlink,
201
                    "AnalysisRequest": arlink,
202
                },
203
                "before": {},
204
                "after": {},
205
                "choices": {},
206
                "class": {"Batch": "Title"},
207
                "state_class": "state-active subgroup_{0}".format(sub_class) if sub_class else "state-active",
208
                "allow_edit": [],
209
                "Batch": "",
210
                "SamplePoint": ar.getSamplePoint().Title() if ar.getSamplePoint() else "",
211
                "SampleType": ar.getSampleType().Title() if ar.getSampleType() else "",
212
                "ClientOrderNumber": ar.getClientOrderNumber(),
213
                "AnalysisRequest": "",
214
                "state_title": state_title,
215
            }
216
            items.append(item)
217
218
        unitstr = '<em class="discreet" style="white-space:nowrap;">%s</em>'
219
220
        # Insert columns for analyses
221
        for d_a in distinct:
222
            keyword = d_a.getKeyword()
223
            short = d_a.getShortTitle()
224
            title = d_a.Title()
225
226
            self.columns[keyword] = {
227
                "title":  short if short else title,
228
                "sortable": False,
229
            }
230
            self.review_states[0]["columns"].insert(
231
                len(self.review_states[0]["columns"]) - 1, keyword)
232
233
            # Insert values for analyses
234
            for i, item in enumerate(items):
235
                for analysis in analyses[item["id"]]:
236
                    if keyword not in items[i]:
237
                        items[i][keyword] = ""
238
                    if analysis.getKeyword() != keyword:
239
                        continue
240
241
                    # check if the user can edit the analysis result
242
                    can_edit_result = self.can_edit_analysis_result(analysis)
243
244
                    calculation = analysis.getCalculation()
245
                    if self.allow_edit and can_edit_result and not calculation:
246
                        items[i]["allow_edit"].append(keyword)
247
                        self.columns[keyword]["ajax"] = True
248
249
                    value = analysis.getResult()
250
                    items[i][keyword] = value
251
                    items[i]["class"][keyword] = ""
252
253
                    if value or (can_edit_result and not calculation):
254
                        unit = unitstr % d_a.getUnit()
255
                        items[i]["after"][keyword] = unit
256
257
                if keyword not in items[i]["class"]:
258
                    items[i]["class"][keyword] = "empty"
259
260
        self.categories.sort()
261
        self.categories = [x[1] for x in self.categories]
262
263
        items = sorted(items, key=itemgetter("sort_key"), reverse=True)
264
265
        return items
266