Passed
Push — master ( 1b1a5c...7b3c17 )
by Jordi
05:45
created

bika.lims.browser.partition_magic   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 330
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 50
eloc 219
dl 0
loc 330
rs 8.4
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
D PartitionMagicView.__call__() 0 60 12
A PartitionMagicView.get_sampletypes() 0 11 1
A PartitionMagicView.add_status_message() 0 4 1
A PartitionMagicView.to_super_model() 0 6 1
A PartitionMagicView.__init__() 0 5 1
A PartitionMagicView.redirect() 0 8 3
A PartitionMagicView.get_ar_data() 0 12 2
A PartitionMagicView.get_sampletype_data() 0 6 2
B PartitionMagicView.create_partition() 0 39 5
A PartitionMagicView.get_object_by_uid() 0 8 2
A PartitionMagicView.is_proxy_field() 0 4 1
A PartitionMagicView.get_number_of_partitions_for() 0 19 4
A PartitionMagicView.is_computed_field() 0 4 1
A PartitionMagicView.get_objects() 0 12 3
A PartitionMagicView.get_specifications_for() 0 7 2
A PartitionMagicView.get_analysis_data_for() 0 12 2
B PartitionMagicView.get_template_data_for() 0 38 6
A PartitionMagicView.get_base_info() 0 15 1

How to fix   Complexity   

Complexity

Complex classes like bika.lims.browser.partition_magic 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
from collections import OrderedDict
4
from collections import defaultdict
5
6
from bika.lims import bikaMessageFactory as _
7
from bika.lims import logger
8
from bika.lims.decorators import returns_super_model
9
from bika.lims.workflow import doActionFor
10
from bika.lims import api
11
from bika.lims.interfaces import IProxyField
12
from bika.lims.utils.analysisrequest import create_analysisrequest as crar
13
from Products.Five.browser import BrowserView
14
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
15
16
DEFAULT_NUMBER_OF_PARTITIONS = 0
17
18
PARTITION_SKIP_FIELDS = [
19
    "Analyses",
20
    "Attachment",
21
    "Client",
22
    "Profile",
23
    "Profiles",
24
    "RejectionReasons",
25
    "Remarks",
26
    "ResultsInterpretation",
27
    "Sample",
28
    "SampleType",
29
    "Template",
30
    "creation_date",
31
    "id",
32
    "modification_date",
33
    "ParentAnalysisRequest",
34
]
35
36
37
class PartitionMagicView(BrowserView):
38
    """Manage Partitions of primary ARs
39
    """
40
    template = ViewPageTemplateFile("templates/partition_magic.pt")
41
42
    def __init__(self, context, request):
43
        super(PartitionMagicView, self).__init__(context, request)
44
        self.context = context
45
        self.request = request
46
        self.back_url = self.context.absolute_url()
47
48
    def __call__(self):
49
        form = self.request.form
50
51
        # Form submit toggle
52
        form_submitted = form.get("submitted", False)
53
54
        # Buttons
55
        form_preview = form.get("button_preview", False)
56
        form_create = form.get("button_create", False)
57
        form_cancel = form.get("button_cancel", False)
58
59
        objs = self.get_objects()
60
61
        # No ARs selected
62
        if not objs:
63
            return self.redirect(message=_("No items selected"),
64
                                 level="warning")
65
66
        # Handle preview
67
        if form_submitted and form_preview:
68
            logger.info("*** PREVIEW ***")
69
70
        # Handle create
71
        if form_submitted and form_create:
72
            logger.info("*** CREATE PARTITIONS ***")
73
74
            partitions = []
75
76
            # create the partitions
77
            for partition in form.get("partitions", []):
78
                primary_uid = partition.get("primary_uid")
79
                sampletype_uid = partition.get("sampletype_uid")
80
                analyses_uids = partition.get("analyses")
81
                if not analyses_uids or not primary_uid:
82
                    # Cannot create a partition w/o analyses!
83
                    continue
84
85
                partition = self.create_partition(
86
                    primary_uid, sampletype_uid, analyses_uids)
87
                partitions.append(partition)
88
                logger.info("Successfully created partition: {}".format(
89
                    api.get_path(partition)))
90
91
                # Force the reception of the partition
92
                doActionFor(partition, "receive")
93
94
            if not partitions:
95
                # If no partitions were created, show a warning message
96
                return self.redirect(message=_("No partitions were created"))
97
98
            message = _("Created {} partitions: {}".format(
99
                len(partitions), ", ".join(map(api.get_title, partitions))))
100
            return self.redirect(message=message)
101
102
        # Handle cancel
103
        if form_submitted and form_cancel:
104
            logger.info("*** CANCEL ***")
105
            return self.redirect(message=_("Partitioning canceled"))
106
107
        return self.template()
108
109
    def create_partition(self, primary_uid, sampletype_uid, analyses_uids):
110
        """Create a new partition (AR)
111
        """
112
        logger.info("*** CREATE PARTITION ***")
113
114
        ar = self.get_object_by_uid(primary_uid)
115
        record = {
116
            "InternalUse": True,
117
            "ParentAnalysisRequest": primary_uid,
118
            "SampleType": sampletype_uid,
119
        }
120
121
        for fieldname, field in api.get_fields(ar).items():
122
            # if self.is_proxy_field(field):
123
            #     logger.info("Skipping proxy field {}".format(fieldname))
124
            #     continue
125
            if self.is_computed_field(field):
126
                logger.info("Skipping computed field {}".format(fieldname))
127
                continue
128
            if fieldname in PARTITION_SKIP_FIELDS:
129
                logger.info("Skipping field {}".format(fieldname))
130
                continue
131
            fieldvalue = field.get(ar)
132
            record[fieldname] = fieldvalue
133
            logger.info("Update record '{}': {}".format(
134
                fieldname, repr(fieldvalue)))
135
136
        client = ar.getClient()
137
        analyses = map(self.get_object_by_uid, analyses_uids)
138
        services = map(lambda an: an.getAnalysisService(), analyses)
139
140
        ar = crar(
141
            client,
142
            self.request,
143
            record,
144
            analyses=services,
145
            specifications=self.get_specifications_for(ar)
146
        )
147
        return ar
148
149
    def get_specifications_for(self, ar):
150
        """Returns a mapping of service uid -> specification
151
        """
152
        spec = ar.getSpecification()
153
        if not spec:
154
            return []
155
        return spec.getResultsRange()
156
157
    def is_proxy_field(self, field):
158
        """Checks if the field is a proxy field
159
        """
160
        return IProxyField.providedBy(field)
161
162
    def is_computed_field(self, field):
163
        """Checks if the field is a coumputed field
164
        """
165
        return field.type == "computed"
166
167
    def get_ar_data(self):
168
        """Returns a list of AR data
169
        """
170
        for obj in self.get_objects():
171
            info = self.get_base_info(obj)
172
            info.update({
173
                "analyses": self.get_analysis_data_for(obj),
174
                "sampletype": self.get_base_info(obj.getSampleType()),
175
                "number_of_partitions": self.get_number_of_partitions_for(obj),
176
                "template": self.get_template_data_for(obj),
177
            })
178
            yield info
179
180
    def get_sampletype_data(self):
181
        """Returns a list of SampleType data
182
        """
183
        for obj in self.get_sampletypes():
184
            info = self.get_base_info(obj)
185
            yield info
186
187
    def get_objects(self):
188
        """Returns a list of objects coming from the "uids" request parameter
189
        """
190
        # Create a mapping of source ARs for copy
191
        uids = self.request.form.get("uids", "")
192
        if not uids:
193
            # check for the `items` parammeter
194
            uids = self.request.form.get("items", "")
195
        if isinstance(uids, basestring):
196
            uids = uids.split(",")
197
        unique_uids = OrderedDict().fromkeys(uids).keys()
198
        return filter(None, map(self.get_object_by_uid, unique_uids))
199
200
    def get_sampletypes(self):
201
        """Returns the available SampleTypes of the system
202
        """
203
        query = {
204
            "portal_type": "SampleType",
205
            "sort_on": "sortable_title",
206
            "sort_order": "ascending",
207
            "inactive_state": "active",
208
        }
209
        results = api.search(query, "bika_setup_catalog")
210
        return map(api.get_object, results)
211
212
    @returns_super_model
213
    def to_super_model(self, obj_or_objs):
214
        """Returns a SuperModel for a given object or a list of Supermodels if
215
        a list of objects was passed in
216
        """
217
        return obj_or_objs
218
219
    def get_analysis_data_for(self, ar):
220
        """Return the Analysis data for this AR
221
        """
222
        analyses = ar.getAnalyses()
223
        out = []
224
        for an in analyses:
225
            info = self.get_base_info(an)
226
            info.update({
227
                "service_uid": an.getServiceUID,
228
            })
229
            out.append(info)
230
        return out
231
232
    def get_template_data_for(self, ar):
233
        """Return the Template data for this AR
234
        """
235
        info = None
236
        template = ar.getTemplate()
237
        ar_sampletype_uid = api.get_uid(ar.getSampleType())
238
239
        if template:
240
            info = self.get_base_info(template)
241
242
            analyses = template.getAnalyses()
243
            partition_analyses = map(
244
                lambda x: (x.get("partition"), x.get("service_uid")), analyses)
245
246
            analyses_by_partition = defaultdict(list)
247
            for partition, service_uid in partition_analyses:
248
                analyses_by_partition[partition].append(service_uid)
249
250
            sampletypes_by_partition = defaultdict(list)
251
            for part in template.getPartitions():
252
                part_id = part.get("part_id")
253
                sampletype_uid = part.get('sampletype_uid', ar_sampletype_uid)
254
                sampletypes_by_partition[part_id] = sampletype_uid
255
256
            partitions = map(lambda p: p.get("part_id"),
257
                             template.getPartitions())
258
            info.update({
259
                "analyses": analyses_by_partition,
260
                "partitions": partitions,
261
                "sample_types": sampletypes_by_partition,
262
            })
263
        else:
264
            info = {
265
                "analyses": {},
266
                "partitions": [],
267
                "sample_types": {},
268
            }
269
        return info
270
271
    def get_number_of_partitions_for(self, ar):
272
        """Return the number of selected partitions
273
        """
274
        # fetch the number of partitions from the request
275
        uid = api.get_uid(ar)
276
        num = self.request.get("primary", {}).get(uid)
277
278
        if num is None:
279
            # get the number of partitions from the template
280
            template = ar.getTemplate()
281
            if template:
282
                num = len(template.getPartitions())
283
            else:
284
                num = DEFAULT_NUMBER_OF_PARTITIONS
285
        try:
286
            num = int(num)
287
        except (TypeError, ValueError):
288
            num = DEFAULT_NUMBER_OF_PARTITIONS
289
        return num
290
291
    def get_base_info(self, obj):
292
        """Extract the base info from the given object
293
        """
294
        obj = api.get_object(obj)
295
        review_state = api.get_workflow_status_of(obj)
296
        state_title = review_state.capitalize().replace("_", " ")
297
        return {
298
            "obj": obj,
299
            "id": api.get_id(obj),
300
            "uid": api.get_uid(obj),
301
            "title": api.get_title(obj),
302
            "path": api.get_path(obj),
303
            "url": api.get_url(obj),
304
            "review_state": review_state,
305
            "state_title": state_title,
306
        }
307
308
    def redirect(self, redirect_url=None, message=None, level="info"):
309
        """Redirect with a message
310
        """
311
        if redirect_url is None:
312
            redirect_url = self.back_url
313
        if message is not None:
314
            self.add_status_message(message, level)
315
        return self.request.response.redirect(redirect_url)
316
317
    def get_object_by_uid(self, uid):
318
        """Get the object by UID
319
        """
320
        logger.debug("get_object_by_uid::UID={}".format(uid))
321
        obj = api.get_object_by_uid(uid, None)
322
        if obj is None:
323
            logger.warn("!! No object found for UID #{} !!")
324
        return obj
325
326
    def add_status_message(self, message, level="info"):
327
        """Set a portal status message
328
        """
329
        return self.context.plone_utils.addPortalMessage(message, level)
330