Passed
Push — 2.x ( 488b85...c0712a )
by Jordi
06:35
created

senaite.core.browser.samples.multi_results_transposed   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 271
Duplicated Lines 10.33 %

Importance

Changes 0
Metric Value
wmc 33
eloc 158
dl 28
loc 271
rs 9.76
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A MultiResultsTransposedView.get_analyses() 0 13 4
A MultiResultsTransposedView.get_objects_from_request() 0 5 1
B MultiResultsTransposedView.folderitems() 0 51 5
B MultiResultsTransposedView.transpose_item() 11 48 6
A MultiResultsTransposedView.get_object_by_uid() 0 8 2
A MultiResultsTransposedView.make_empty_folderitem() 0 7 1
A MultiResultsTransposedView.__init__() 0 47 1
A MultiResultsTransposedView.uniquify_items() 0 9 3
A MultiResultsTransposedView.get_sample_folderitems() 0 9 1
A MultiResultsTransposedView.get_updated_samples() 0 12 3
A MultiResultsTransposedView.get_uids_from_request() 0 10 2
A MultiResultsTransposedView.get_samples() 17 17 4

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
# -*- coding: utf-8 -*-
2
3
from collections import OrderedDict
4
5
from bika.lims import api
6
from bika.lims import bikaMessageFactory as _
7
from bika.lims.browser.analyses import AnalysesView
8
from bika.lims.browser.worksheet.views import AnalysesTransposedView
9
from bika.lims.interfaces import IAnalysisRequest
10
from bika.lims.utils import get_link
11
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
12
from senaite.core import logger
13
from senaite.core.i18n import translate as t
14
from six.moves.urllib.parse import parse_qs
15
16
17
class MultiResultsTransposedView(AnalysesTransposedView):
18
    """Transposed multi results view
19
    """
20
    template = ViewPageTemplateFile("templates/multi_results.pt")
21
22
    def __init__(self, context, request):
23
        super(MultiResultsTransposedView, self).__init__(context, request)
24
25
        self.allow_edit = True
26
        self.expand_all_categories = False
27
        self.show_categories = False
28
        self.show_column_toggles = False
29
        self.show_search = False
30
        self.show_select_column = True
31
32
        # NOTE: The contentFilter query is set by `senaite.app.listing` to
33
        # update a specific folderitem. In our case, this might be the UID of
34
        # one or more Analyses.
35
        # It is used to generate the folderitems for all the selected samples.
36
        # We are also only interested in lab and field analyses to not show
37
        # other PoCs coming from e.g. `senaite.ast`.
38
        self.contentFilter = {
39
            "portal_type": "Analysis",
40
            "getPointOfCapture": ["lab", "field"],
41
        }
42
43
        self.transposed = True
44
        self.classic_url = "{}/multi_results_classic?uids={}".format(
45
            self.context.absolute_url(), self.request.form.get("uids"))
46
47
        self.title = _("Multi Results")
48
        self.description = _("")
49
50
        self.headers = OrderedDict()
51
        self.services = OrderedDict()
52
53
        self.columns = OrderedDict((
54
            ("column_key", {
55
                "title": "",
56
                "sortable": False}),
57
            ("Result", {
58
                "title": _("Result"),
59
                "ajax": True,
60
                "sortable": False}),
61
        ))
62
63
        self.review_states = [
64
            {
65
                "id": "default",
66
                "title": _("All"),
67
                "custom_transitions": [],
68
                "columns": self.columns.keys(),
69
            },
70
        ]
71
72
    def make_empty_folderitem(self, **kw):
73
        """Create a new empty item
74
        """
75
        item = AnalysesView.make_empty_folderitem(self, **kw)
76
        item["transposed_keys"] = []
77
        item.update(**kw)
78
        return item
79
80
    def transpose_item(self, item, pos):
81
        """Transpose the folderitem
82
        """
83
        obj = api.get_object(item["obj"])
84
        service = item["Service"]
85
        keyword = obj.getKeyword()
86
        item["Pos"] = pos
87
88
        # skip retracted analyses
89
        review_state = item["review_state"]
90
        if review_state in ["retracted"]:
91
            return item
92
93
        # show only retests
94
        if obj.getRetest():
95
            return item
96
97
        # remember the column headers of the first row
98
        if "Pos" not in self.headers:
99
            self.headers["Pos"] = self.make_empty_folderitem(
100
                column_key=t(_("Position")), item_key="Pos")
101
102
        # remember the services, e.g. Calcium, Magnesium, Total Hardness etc.
103 View Code Duplication
        if keyword not in self.services:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
104
            transposed_item = self.make_empty_folderitem(
105
                column_key=keyword, item_key="Result")
106
            # Append info link after the service
107
            transposed_item["after"]["column_key"] = get_link(
108
                "analysisservice_info?service_uid={}&analysis_uid={}"
109
                .format(item["service_uid"], item["uid"]),
110
                value="<i class='fas fa-info-circle'></i>",
111
                css_class="overlay_panel")
112
            transposed_item["replace"]["column_key"] = service
113
            self.services[keyword] = transposed_item
114
115
        # append all regular items that belong to this service
116
        if pos not in self.services[keyword]:
117
            header_item = self.make_empty_folderitem()
118
            # Add the item with the Pos header
119
            header_item["replace"]["Pos"] = self.get_slot_header(item)
120
            # Append to header
121
            self.headers["Pos"][pos] = header_item
122
            # Add the item below its position
123
            self.services[keyword][pos] = item
124
            # Track the new transposed key for this item
125
            self.services[keyword]["transposed_keys"].append(pos)
126
127
        return item
128
129
    def get_updated_samples(self):
130
        """Returns samples where analyses have been updated
131
        """
132
        updated_samples = []
133
        uids = self.contentFilter.get("UID", [])
134
        for uid in uids:
135
            analysis = api.get_object(uid)
136
            sample = analysis.getRequest()
137
            if sample in updated_samples:
138
                continue
139
            updated_samples.append(sample)
140
        return updated_samples
141
142
    def folderitems(self):
143
        """Prepare transposed analyses folderitems
144
145
        NOTE: This method is called by a separate Ajax request, which creates
146
              a new view instance!
147
148
              Therefore, we do all dynamic modifications to the columns etc.
149
              right here to avoid expensive operations called multiple times!
150
        """
151
        samples = self.get_samples()
152
153
        # This is added for performance reasons to fetch only those folderitems
154
        # (analyses) from samples, that were updated (submitted, verified etc.)
155
        # The content filter UID query is set by senaite.app.listing
156
        updated_samples = self.get_updated_samples()
157
158
        for num, sample in enumerate(samples):
159
            slot = str(num + 1)
160
            # Create a new column for the sample at the right position
161
            self.columns[slot] = {
162
                "title": "",
163
                "type": "transposed",
164
                "sortable": False,
165
            }
166
            # we can skip to fetch folderitems if the contentFilter contains a
167
            # UID query for updated Analyses.
168
            if updated_samples and sample not in updated_samples:
169
                continue
170
171
            for item in self.get_sample_folderitems(sample):
172
                transposed = self.transpose_item(item, slot)
173
174
        # show service and sample columns
175
        slots = [str(i + 1) for i in range(len(samples))]
176
        self.review_states[0]["columns"] = ["column_key"] + slots
177
178
        # transposed rows holder
179
        transposed = OrderedDict()
180
181
        # HTML slot headers
182
        transposed.update(self.headers)
183
184
        # collected services (Iron, Copper, Calcium...)
185
        services = OrderedDict(reversed(self.services.items()))
186
        transposed.update(services)
187
188
        # listing fixtures
189
        self.total = len(transposed.keys())
190
191
        # return the transposed rows
192
        return transposed.values()
193
194
    def get_sample_folderitems(self, sample):
195
        """Get the folderitems for the given sample
196
        """
197
        view = AnalysesView(sample, self.request)
198
        # Inject the updated contentFilter query that might contain the UIDs of
199
        # the updated Analyses.
200
        view.contentFilter = dict(self.contentFilter)
201
        view.contentFilter["getAncestorsUIDs"] = [api.get_uid(sample)]
202
        return view.folderitems()
203
204
    def get_analyses(self, full_objects=False):
205
        """Returns sample analyses from lab poc
206
        """
207
        analyses = []
208
209
        # get analyses of all samples
210
        for sample in self.get_samples():
211
            for analysis in sample.getAnalyses(**self.contentFilter):
212
                if full_objects:
213
                    analysis = api.get_object(analysis)
214
                analyses.append(analysis)
215
216
        return analyses
217
218 View Code Duplication
    def get_samples(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
219
        """Extract the samples from the request UIDs
220
        """
221
        objs = self.get_objects_from_request()
222
223
        samples = []
224
225
        for obj in objs:
226
            # when coming from the samples listing
227
            if IAnalysisRequest.providedBy(obj):
228
                samples.append(obj)
229
230
        # when coming from the WF menu inside a sample
231
        if IAnalysisRequest.providedBy(self.context):
232
            samples.append(self.context)
233
234
        return self.uniquify_items(samples)
235
236
    def uniquify_items(self, items):
237
        """Uniquify the items with sort order
238
        """
239
        unique = []
240
        for item in items:
241
            if item in unique:
242
                continue
243
            unique.append(item)
244
        return unique
245
246
    def get_objects_from_request(self):
247
        """Returns a list of objects coming from the "uids" request parameter
248
        """
249
        unique_uids = self.get_uids_from_request()
250
        return filter(None, map(self.get_object_by_uid, unique_uids))
251
252
    def get_uids_from_request(self):
253
        """Return a list of uids from the request
254
        """
255
        qs = self.request.get_header("query_string")
256
        params = dict(parse_qs(qs))
257
        uids = params.get("uids", [""])[0]
258
        if api.is_string(uids):
259
            uids = uids.split(",")
260
        unique_uids = OrderedDict().fromkeys(uids).keys()
261
        return filter(api.is_uid, unique_uids)
262
263
    def get_object_by_uid(self, uid):
264
        """Get the object by UID
265
        """
266
        logger.debug("get_object_by_uid::UID={}".format(uid))
267
        obj = api.get_object_by_uid(uid, None)
268
        if obj is None:
269
            logger.warn("!! No object found for UID #{} !!")
270
        return obj
271