Passed
Push — 2.x ( 4e3b0a...8119a4 )
by Jordi
07:03
created

senaite.core.browser.form.adapters.worksheettemplate   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 279
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 51
eloc 200
dl 0
loc 279
rs 7.92
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A EditForm.toggle_fields() 0 11 2
A EditForm.get_reference_definitions() 0 12 4
A EditForm.toggle_field() 0 5 2
A EditForm.initialized() 0 4 1
A EditForm.init_toggle_fields() 0 11 3
A EditForm.update_duplicate_items() 0 21 5
B EditForm.validate_duplicate() 0 23 5
A EditForm.add_change_num_positions() 0 5 1
A EditForm.toggle_duplicate_field() 0 6 3
A EditForm.modify_positions() 0 12 2
A EditForm.added() 0 2 1
A EditForm.get_instruments_options() 0 19 3
A EditForm.get_count_rows() 0 4 1
C EditForm.modified() 0 40 9
A EditForm.callback() 0 8 3
B EditForm.toggle_reference_field() 0 17 6

How to fix   Complexity   

Complexity

Complex classes like senaite.core.browser.form.adapters.worksheettemplate 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-2024 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
import re
22
from string import Template
23
24
from bika.lims import api
25
from bika.lims import senaiteMessageFactory as _
26
from senaite.core.browser.form.adapters import EditFormAdapterBase
27
from senaite.core.catalog import SETUP_CATALOG
28
from senaite.core.i18n import translate
29
30
pos_regex = re.compile(r"(\d+)\.widgets\.pos$")
31
type_regex = re.compile(r"(\d+)\.widgets\.type$")
32
dup_proxy_regex = re.compile(r"(\d+)\.widgets\.dup_proxy$")
33
ref_proxy_regex = re.compile(r"(\d+)\.widgets\.reference_proxy$")
34
35
POS_PARENT_SELECTOR = "td:has(>[name='{}'])"
36
NUM_POS_SELECTOR = "#formfield-form-widgets-num_of_positions > .input-group"
37
POS_DIV_BLOCK = "<div style='width: 85%; text-align: center;'>{}</div>"
38
FIELD_NUM_POSITIONS = "form.widgets.num_of_positions"
39
FIELD_LAYOUT = "form.widgets.template_layout"
40
FIELD_POS = "form.widgets.template_layout.{}.widgets.pos"
41
FIELD_TYPE = "form.widgets.template_layout.{}.widgets.type:list"
42
FIELD_BLANK  = "form.widgets.template_layout.{}.widgets.blank_ref"
43
FIELD_CONTROL  = "form.widgets.template_layout.{}.widgets.control_ref"
44
FIELD_DUP = "form.widgets.template_layout.{}.widgets.dup"
45
FIELD_DUP_PROXY = "form.widgets.template_layout.{}.widgets.dup_proxy:list"
46
FIELD_REF_PROXY = "form.widgets.template_layout.{}.widgets.reference_proxy:list"
47
48
NUM_POS_HTML = Template("""
49
<input
50
    type="submit"
51
    class="button-num-positions btn-primary"
52
    formaction="$absolute_url/@@update_num_positions"
53
    value="$title"
54
/>
55
""")
56
57
58
class EditForm(EditFormAdapterBase):
59
    """Edit form adapter for Worksheet Template
60
    """
61
62
    def initialized(self, data):
63
        self.add_change_num_positions(data)
64
        self.init_toggle_fields(data)
65
        return self.data
66
67
    def init_toggle_fields(self, data):
68
        form = data.get("form")
69
        if int(form.get(FIELD_NUM_POSITIONS, "0")) == 0:
70
            self.toggle_field(FIELD_LAYOUT, False)
71
        else:
72
            self.modify_positions(data)
73
            count_rows = self.get_count_rows(data)
74
            for index in range(count_rows):
75
                field = FIELD_TYPE.format(index)
76
                analysis_type = form.get(field, "a")
77
                self.toggle_fields(data, analysis_type, index)
78
79
    def added(self, data):
80
        return self.data
81
82
    def callback(self, data):
83
        name = data.get("name")
84
        if not name:
85
            return
86
        method = getattr(self, name, None)
87
        if not callable(method):
88
            return
89
        return method(data)
90
91
    def modified(self, data):
92
        name = data.get("name")
93
        value = data.get("value")
94
95
        type_match = type_regex.search(name)
96
        dup_match = dup_proxy_regex.search(name)
97
        ref_match = ref_proxy_regex.search(name)
98
99
        if name == "form.widgets.restrict_to_method" and value:
100
            method_uid = value[0]
101
            options = self.get_instruments_options(method_uid)
102
            self.add_update_field("form.widgets.instrument", {
103
                "options": options
104
            })
105
        elif type_match:
106
            idx = type_match.group(1)
107
            val = value[0]
108
            self.toggle_fields(data, val, idx)
109
        elif dup_match:
110
            idx = dup_match.group(1)
111
            if value:
112
                val = value[0]
113
                self.add_update_field(FIELD_DUP.format(idx), val)
114
            else:
115
                msg = translate(_(
116
                    u"duplicate_reference_not_found",
117
                    default=u"Not found Analysis position for duplicate."))
118
                self.add_error_field(FIELD_DUP_PROXY.format(idx), msg)
119
        elif ref_match:
120
            idx = ref_match.group(1)
121
            form = data.get("form")
122
            analysis_type = form.get(FIELD_TYPE.format(idx))
123
            if analysis_type == "b":
124
                self.add_update_field(FIELD_BLANK.format(idx), value)
125
                self.add_update_field(FIELD_CONTROL.format(idx), "")
126
            elif analysis_type == "c":
127
                self.add_update_field(FIELD_BLANK.format(idx), "")
128
                self.add_update_field(FIELD_CONTROL.format(idx), value)
129
130
        return self.data
131
132
    def toggle_fields(self, data, field_type, index):
133
        self.add_error_field(FIELD_TYPE.format(index), "")
134
        if field_type == "a":
135
            self.add_update_field(FIELD_BLANK.format(index), "")
136
            self.add_update_field(FIELD_CONTROL.format(index), "")
137
            self.add_update_field(FIELD_REF_PROXY.format(index), "")
138
            self.add_update_field(FIELD_DUP_PROXY.format(index), "")
139
            self.add_update_field(FIELD_DUP.format(index), "")
140
141
        self.toggle_reference_field(data, index, field_type)
142
        self.toggle_duplicate_field(data, index, field_type)
143
144
    def toggle_duplicate_field(self, data, index, field_type):
145
        field = FIELD_DUP_PROXY.format(index)
146
        toggle = field_type == "d"
147
        if toggle and self.validate_duplicate(data, index):
148
            self.update_duplicate_items(data, index)
149
        self.toggle_field(field, toggle)
150
151
    def validate_duplicate(self, data, index):
152
        form = data.get("form")
153
        current_pos = int(form.get(FIELD_POS.format(index)))
154
        count_rows = self.get_count_rows(data)
155
        dup_pos = 0
156
        for i in range(count_rows):
157
            dup_value = form.get(FIELD_DUP.format(i))
158
            if not dup_value:
159
                continue
160
            if int(dup_value) == current_pos:
161
                dup_pos = int(form.get(FIELD_POS.format(i)))
162
                break
163
164
        if dup_pos:
165
            msg = translate(_(
166
                u"duplicate_reference_this_position",
167
                default=u"Duplicate in position ${dup} references this, "
168
                        u"so it must be a routine analysis.",
169
                mapping={"dup": dup_pos})
170
            )
171
            self.add_error_field(FIELD_TYPE.format(index), msg)
172
            return False
173
        return True
174
175
    def toggle_reference_field(self, data, index, field_type):
176
        field = FIELD_REF_PROXY.format(index)
177
        toggle = field_type in ["b", "c"]
178
        field_ref = FIELD_BLANK if field_type == "b" else FIELD_CONTROL
179
        field_ref = field_ref.format(index)
180
        self.toggle_field(field, toggle)
181
        if toggle and self.validate_duplicate(data, index):
182
            form = data.get("form")
183
            options = self.get_reference_definitions(field_type)
184
            selected = form.get(field_ref)
185
            if len(options) and not selected:
186
                selected = options[0].get("value")
187
                self.add_update_field(field_ref, selected)
188
189
            self.add_update_field(field, {
190
                "options": options,
191
                "selected": selected,
192
            })
193
194
    def toggle_field(self, field, toggle=False):
195
        if toggle:
196
            self.add_show_field(field)
197
        else:
198
            self.add_hide_field(field)
199
200
    def update_duplicate_items(self, data, index):
201
        """Updating list of allowed duplicate values
202
        """
203
        form = data.get("form")
204
        includes = set()
205
        count_rows = self.get_count_rows(data)
206
        for i in range(count_rows):
207
            select_type = form.get(FIELD_TYPE.format(i))
208
            if select_type == "a":
209
                includes.add(form.get(FIELD_POS.format(i)))
210
211
        field_dup = FIELD_DUP.format(index)
212
        options = [dict(value=pos, title=pos) for pos in includes]
213
        selected = form.get(field_dup)
214
        if len(includes) and not selected:
215
            selected = next(iter(includes))
216
            self.add_update_field(field_dup, selected)
217
218
        self.add_update_field(FIELD_DUP_PROXY.format(index), {
219
            "options": options,
220
            "selected": selected,
221
        })
222
223
    def get_instruments_options(self, method):
224
        """Returns a list of dicts that represent instrument options suitable
225
        for a selection list, with an empty option as first item
226
        """
227
        options = [{
228
            "title": _(u"form_widget_instrument_title",
229
                       default=u"No Instrument"),
230
            "value": [""]
231
        }]
232
        method = api.get_object(method, default=None)
233
        instruments = method.getInstruments() if method else []
234
        for instrument in instruments:
235
            option = {
236
                "title": api.get_title(instrument),
237
                "value": api.get_uid(instrument)
238
            }
239
            options.append(option)
240
241
        return options
242
243
    def add_change_num_positions(self, data):
244
        url = self.context.absolute_url()
245
        title = translate(_(u"num_of_positions_button_title", default=u"Set"))
246
        html = NUM_POS_HTML.safe_substitute(absolute_url=url, title=title)
247
        self.add_inner_html(NUM_POS_SELECTOR, html, append=True)
248
249
    def modify_positions(self, data):
250
        """Replacing input control to text for positions
251
        """
252
        count_rows = self.get_count_rows(data)
253
        for i in range(count_rows):
254
            field = FIELD_POS.format(i)
255
            pos = str(i + 1)
256
            self.add_update_field(field, pos)
257
            self.add_hide_field(field)
258
            selector = POS_PARENT_SELECTOR.format(field)
259
            html = POS_DIV_BLOCK.format(pos)
260
            self.add_inner_html(selector, html, append=True)
261
262
    def get_count_rows(self, data):
263
        form = data.get("form")
264
        positions = [k for k in form.keys() if pos_regex.search(k)]
265
        return len(positions)
266
267
    def get_reference_definitions(self, reference_type):
268
        reference_query = {
269
            "portal_type": "ReferenceDefinition",
270
            "is_active": True,
271
        }
272
        brains = api.search(reference_query, SETUP_CATALOG)
273
        definitions = map(api.get_object, brains)
274
        if  reference_type == "b":
275
            definitions = filter(lambda d: d.getBlank(), definitions)
276
        else:
277
            definitions = filter(lambda d: not d.getBlank(), definitions)
278
        return [dict(value=d.UID(), title=d.Title()) for d in definitions]
279