Passed
Push — 2.x ( d668b2...78a11b )
by Ramon
05:54
created

bika.lims.controlpanel.bika_analysisservices   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 414
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 41
eloc 294
dl 0
loc 414
rs 9.1199
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A AnalysisServicesView.get_decimal_mark() 0 4 1
A AnalysisServicesView.can_edit() 0 4 1
C AnalysisServiceCopy.__call__() 0 47 10
A AnalysisServicesView.folderitems() 0 17 4
A AnalysisServicesView.format_maxtime() 0 9 1
A AnalysisServicesView.format_duplication_variation() 0 7 1
F AnalysisServicesView.folderitem() 0 86 14
A AnalysisServicesView.get_currency_symbol() 0 9 2
A AnalysisServicesView.format_price() 0 8 1
A AnalysisServiceCopy.copy_service() 0 14 2
B AnalysisServicesView.__init__() 0 123 4

How to fix   Complexity   

Complexity

Complex classes like bika.lims.controlpanel.bika_analysisservices 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-2021 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
import collections
22
23
from bika.lims import api
24
from bika.lims import bikaMessageFactory as _
25
from bika.lims.api import analysisservice as serviceapi
26
from bika.lims.api.security import check_permission
27
from bika.lims.browser.bika_listing import BikaListingView
28
from bika.lims.config import PROJECTNAME
29
from bika.lims.interfaces import IAnalysisServices
30
from bika.lims.permissions import AddAnalysisService
31
from bika.lims.utils import format_supsub
32
from bika.lims.utils import get_image
33
from bika.lims.utils import get_link
34
from plone.app.content.browser.interfaces import IFolderContentsView
35
from plone.app.folder.folder import ATFolder
36
from plone.app.folder.folder import ATFolderSchema
37
from plone.app.layout.globals.interfaces import IViewView
38
from Products.Archetypes import atapi
39
from Products.ATContentTypes.content.schemata import finalizeATCTSchema
40
from Products.CMFCore.permissions import ModifyPortalContent
41
from Products.CMFCore.utils import getToolByName
42
from Products.CMFPlone.utils import safe_unicode
43
from Products.Five.browser import BrowserView
44
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
45
from senaite.core.interfaces import IHideActionsMenu
46
from transaction import savepoint
47
from zope.i18n.locales import locales
48
from zope.interface.declarations import implements
49
50
51
class AnalysisServiceCopy(BrowserView):
52
    template = ViewPageTemplateFile("templates/analysisservice_copy.pt")
53
    created = []
54
55
    def copy_service(self, source, title, keyword):
56
        """Creates a copy of the given AnalysisService object, but with the
57
        given title and keyword
58
        """
59
        # Validate the keyword
60
        err_msg = serviceapi.check_keyword(keyword)
61
        if err_msg:
62
            self.context.plone_utils.addPortalMessage(
63
                safe_unicode(err_msg), "error")
64
            return
65
66
        # Create a copy
67
        return api.copy_object(source, title=title, Keyword=keyword,
68
                               ShortTitle="")
69
70
    def __call__(self):
71
        uc = getToolByName(self.context, "uid_catalog")
72
        if "copy_form_submitted" not in self.request:
73
            uids = self.request.form.get("uids", [])
74
            self.services = []
75
            for uid in uids:
76
                proxies = uc(UID=uid)
77
                if proxies:
78
                    self.services.append(proxies[0].getObject())
79
            return self.template()
80
        else:
81
            self.savepoint = savepoint()
82
            sources = self.request.form.get("uids", [])
83
            titles = self.request.form.get("dst_title", [])
84
            keywords = self.request.form.get("dst_keyword", [])
85
            self.created = []
86
            for i, s in enumerate(sources):
87
                if not titles[i]:
88
                    message = _("Validation failed: title is required")
89
                    message = safe_unicode(message)
90
                    self.context.plone_utils.addPortalMessage(message, "info")
91
                    self.savepoint.rollback()
92
                    self.created = []
93
                    break
94
                if not keywords[i]:
95
                    message = _("Validation failed: keyword is required")
96
                    message = safe_unicode(message)
97
                    self.context.plone_utils.addPortalMessage(message, "info")
98
                    self.savepoint.rollback()
99
                    self.created = []
100
                    break
101
                service_copy = self.copy_service(s, titles[i], keywords[i])
102
                if service_copy:
103
                    self.created.append(api.get_title(service_copy))
104
            if len(self.created) > 1:
105
                message = _("${items} were successfully created.",
106
                            mapping={
107
                                "items": safe_unicode(
108
                                    ", ".join(self.created))})
109
            elif len(self.created) == 1:
110
                message = _("${item} was successfully created.",
111
                            mapping={
112
                                "item": safe_unicode(self.created[0])})
113
            else:
114
                message = _("No new items were created.")
115
            self.context.plone_utils.addPortalMessage(message, "info")
116
            self.request.response.redirect(self.context.absolute_url())
117
118
119
class AnalysisServicesView(BikaListingView):
120
    """Listing table view for Analysis Services
121
    """
122
    implements(IFolderContentsView, IViewView)
123
124
    def __init__(self, context, request):
125
        super(AnalysisServicesView, self).__init__(context, request)
126
127
        self.an_cats = None
128
        self.an_cats_order = None
129
        self.catalog = "senaite_catalog_setup"
130
131
        self.contentFilter = {
132
            "portal_type": "AnalysisService",
133
            "sort_on": "sortable_title",
134
            "sort_order": "ascending",
135
        }
136
137
        self.context_actions = {
138
            _("Add"): {
139
                "url": "createObject?type_name=AnalysisService",
140
                "permission": AddAnalysisService,
141
                "icon": "++resource++bika.lims.images/add.png"}
142
        }
143
144
        self.icon = "{}/{}".format(
145
            self.portal_url,
146
            "/++resource++bika.lims.images/analysisservice_big.png"
147
        )
148
149
        self.title = self.context.translate(_("Analysis Services"))
150
        self.form_id = "list_analysisservices"
151
152
        self.show_select_row = False
153
        self.show_select_column = True
154
        self.show_select_all_checkbox = False
155
        self.pagesize = 25
156
        self.sort_on = "sortable_title"
157
        self.categories = []
158
        self.do_cats = self.context.bika_setup.getCategoriseAnalysisServices()
159
        self.can_sort = not self.do_cats
160
        self.currency_symbol = self.get_currency_symbol()
161
        self.decimal_mark = self.get_decimal_mark()
162
        if self.do_cats:
163
            self.pagesize = 999999  # hide batching controls
164
            self.show_categories = True
165
            self.expand_all_categories = False
166
167
        self.columns = collections.OrderedDict((
168
            ("Title", {
169
                "title": _("Service"),
170
                "index": "sortable_title",
171
                "replace_url": "getURL",
172
                "sortable": self.can_sort}),
173
            ("Keyword", {
174
                "title": _("Keyword"),
175
                "index": "getKeyword",
176
                "attr": "getKeyword",
177
                "sortable": self.can_sort}),
178
            ("Category", {
179
                "title": _("Category"),
180
                "attr": "getCategoryTitle",
181
                "sortable": self.can_sort}),
182
            ("Methods", {
183
                "title": _("Methods"),
184
                "sortable": self.can_sort}),
185
            ("Department", {
186
                "title": _("Department"),
187
                "toggle": False,
188
                "sortable": self.can_sort}),
189
            ("Unit", {
190
                "title": _("Unit"),
191
                "sortable": False}),
192
            ("Price", {
193
                "title": _("Price"),
194
                "sortable": self.can_sort}),
195
            ("MaxTimeAllowed", {
196
                "title": _("Max Time"),
197
                "toggle": False,
198
                "sortable": self.can_sort}),
199
            ("DuplicateVariation", {
200
                "title": _("Dup Var"),
201
                "toggle": False,
202
                "sortable": False}),
203
            ("Calculation", {
204
                "title": _("Calculation"),
205
                "sortable": False}),
206
            ("SortKey", {
207
                "ajax": True,
208
                "help": _(
209
                    "Float value from 0.0 - 1000.0 indicating the sort order. "
210
                    "Duplicate values are ordered alphabetically."),
211
                "refetch": True,
212
                "title": _("Sort Key"),
213
                "sortable": False}),
214
        ))
215
216
        copy_transition = {
217
            "id": "duplicate",
218
            "title": _("Duplicate"),
219
            "url": "{}/copy".format(api.get_url(self.context))
220
        }
221
222
        self.review_states = [
223
            {
224
                "id": "default",
225
                "title": _("Active"),
226
                "contentFilter": {"review_state": "active"},
227
                "columns": self.columns.keys(),
228
                "custom_transitions": [copy_transition]
229
            }, {
230
                "id": "inactive",
231
                "title": _("Inactive"),
232
                "contentFilter": {"review_state": "inactive"},
233
                "columns": self.columns.keys(),
234
                "custom_transitions": [copy_transition]
235
            }, {
236
                "id": "all",
237
                "title": _("All"),
238
                "contentFilter": {},
239
                "columns": self.columns.keys(),
240
                "custom_transitions": [copy_transition]
241
            },
242
        ]
243
244
        if not self.context.bika_setup.getShowPrices():
245
            for i in range(len(self.review_states)):
246
                self.review_states[i]["columns"].remove("Price")
247
248
    def can_edit(self, service):
249
        """Check if manage is allowed
250
        """
251
        return check_permission(ModifyPortalContent, self.context)
252
253
    def get_decimal_mark(self):
254
        """Returns the decimal mark
255
        """
256
        return self.context.bika_setup.getDecimalMark()
257
258
    def get_currency_symbol(self):
259
        """Returns the locale currency symbol
260
        """
261
        currency = self.context.bika_setup.getCurrency()
262
        locale = locales.getLocale("en")
263
        locale_currency = locale.numbers.currencies.get(currency)
264
        if locale_currency is None:
265
            return "$"
266
        return locale_currency.symbol
267
268
    def format_price(self, price):
269
        """Formats the price with the set decimal mark and correct currency
270
        """
271
        return u"{} {}{}{:02d}".format(
272
            self.currency_symbol,
273
            price[0],
274
            self.decimal_mark,
275
            price[1],
276
        )
277
278
    def format_maxtime(self, maxtime):
279
        """Formats the max time record to a days, hours, minutes string
280
        """
281
        minutes = maxtime.get("minutes", "0")
282
        hours = maxtime.get("hours", "0")
283
        days = maxtime.get("days", "0")
284
        # days, hours, minutes
285
        return u"{}: {} {}: {} {}: {}".format(
286
            _("days"), days, _("hours"), hours, _("minutes"), minutes)
287
288
    def format_duplication_variation(self, variation):
289
        """Format duplicate variation
290
        """
291
        return u"{}{}{:02d}".format(
292
            variation[0],
293
            self.decimal_mark,
294
            variation[1]
295
        )
296
297
    def folderitem(self, obj, item, index):
298
        """Service triggered each time an item is iterated in folderitems.
299
        The use of this service prevents the extra-loops in child objects.
300
        :obj: the instance of the class to be foldered
301
        :item: dict containing the properties of the object to be used by
302
            the template
303
        :index: current index of the item
304
        """
305
        obj = api.get_object(obj)
306
        cat = obj.getCategoryTitle()
307
        cat_order = self.an_cats_order.get(cat)
308
        if self.do_cats:
309
            # category groups entries
310
            item["category"] = cat
311
            if (cat, cat_order) not in self.categories:
312
                self.categories.append((cat, cat_order))
313
314
        # Category
315
        category = obj.getCategory()
316
        if category:
317
            title = category.Title()
318
            url = api.get_url(category)
319
            item["Category"] = title
320
            item["replace"]["Category"] = get_link(url, value=title)
321
322
        # Calculation
323
        calculation = obj.getCalculation()
324
        if calculation:
325
            title = calculation.Title()
326
            url = api.get_url(calculation)
327
            item["Calculation"] = title
328
            item["replace"]["Calculation"] = get_link(url, value=title)
329
330
        # Methods
331
        methods = obj.getMethods()
332
        if methods:
333
            links = map(
334
                lambda m: get_link(
335
                    m.absolute_url(), value=m.Title(), css_class="link"),
336
                methods)
337
            item["replace"]["Methods"] = ", ".join(links)
338
339
        # Max time allowed
340
        maxtime = obj.MaxTimeAllowed
341
        if maxtime:
342
            item["MaxTimeAllowed"] = self.format_maxtime(maxtime)
343
344
        # Price
345
        item["Price"] = self.format_price(obj.Price)
346
347
        # Duplicate Variation
348
        dup_variation = obj.DuplicateVariation
349
        if dup_variation:
350
            item["DuplicateVariation"] = self.format_duplication_variation(
351
                dup_variation)
352
353
        # Department
354
        department = obj.getDepartment()
355
        if department:
356
            title = api.get_title(department)
357
            url = api.get_url(department)
358
            item["replace"]["Department"] = get_link(url, title)
359
360
        # Unit
361
        unit = obj.getUnit()
362
        item["Unit"] = unit and format_supsub(unit) or ""
363
364
        # Sort key
365
        sortkey = obj.getSortKey()
366
        item["SortKey"] = sortkey
367
368
        if self.can_edit(obj):
369
            item["allow_edit"].append("SortKey")
370
371
        # Icons
372
        after_icons = ""
373
        if obj.getAccredited():
374
            after_icons += get_image(
375
                "accredited.png", title=_("Accredited"))
376
        if obj.getAttachmentRequired():
377
            after_icons += get_image(
378
                "attach_reqd.png", title=_("Attachment required"))
379
        if after_icons:
380
            item["after"]["Title"] = after_icons
381
382
        return item
383
384
    def folderitems(self):
385
        """Sort by Categories
386
        """
387
        bsc = getToolByName(self.context, "senaite_catalog_setup")
388
        self.an_cats = bsc(
389
            portal_type="AnalysisCategory",
390
            sort_on="sortable_title")
391
        self.an_cats_order = dict([
392
            (b.Title, "{:04}".format(a))
393
            for a, b in enumerate(self.an_cats)])
394
        items = super(AnalysisServicesView, self).folderitems()
395
        if self.do_cats:
396
            self.categories = map(lambda x: x[0],
397
                                  sorted(self.categories, key=lambda x: x[1]))
398
        else:
399
            self.categories.sort()
400
        return items
401
402
403
schema = ATFolderSchema.copy()
404
finalizeATCTSchema(schema, folderish=True, moveDiscussion=False)
405
406
407
class AnalysisServices(ATFolder):
408
    implements(IAnalysisServices, IHideActionsMenu)
409
    displayContentsTab = False
410
    schema = schema
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable schema does not seem to be defined.
Loading history...
411
412
413
atapi.registerType(AnalysisServices, PROJECTNAME)
414