Passed
Push — master ( 6d6a8c...6374c8 )
by Ramon
05:17
created

AnalysisServiceCopy.validate_service()   A

Complexity

Conditions 2

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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