Passed
Push — 2.x ( 9e83c7...b29d60 )
by Ramon
08:36 queued 02:51
created

Setup.getEmailFromSamplePublication()   A

Complexity

Conditions 2

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 9
rs 10
c 0
b 0
f 0
cc 2
nop 1
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
from AccessControl import ClassSecurityInfo
22
from bika.lims import api
23
from plone.app.textfield import IRichTextValue
24
from plone.app.textfield.widget import RichTextFieldWidget  # TBD: port to core
25
from plone.autoform import directives
26
from plone.formwidget.namedfile.widget import NamedFileFieldWidget
27
from plone.schema.email import Email
28
from plone.supermodel import model
29
from Products.CMFCore import permissions
30
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
31
from senaite.core.catalog import AUDITLOG_CATALOG
32
from senaite.core.content.base import Container
33
from senaite.core.interfaces import IHideActionsMenu
34
from senaite.core.interfaces import ISetup
35
from senaite.core.schema import RichTextField
36
from senaite.impress import senaiteMessageFactory as _
37
from zope import schema
38
from zope.interface import implementer
39
from zope.interface import provider
40
from zope.schema.interfaces import IContextAwareDefaultFactory
41
42
43
@provider(IContextAwareDefaultFactory)
44
def default_email_body_sample_publication(context):
45
    """Returns the default body text for publication emails
46
    """
47
    view = api.get_view("senaite_view", context=api.get_setup())
48
    if view is None:
49
        # Test fixture
50
        return u""
51
    tpl = ViewPageTemplateFile(
52
        "../browser/setup/templates/email_body_sample_publication.pt")
53
    return tpl(view)
54
55
56
@provider(IContextAwareDefaultFactory)
57
def default_email_from_sample_publication(context):
58
    """Returns the default email 'From' for results reports publish
59
    """
60
    portal_email = api.get_registry_record("plone.email_from_address")
61
    return portal_email
62
63
64
class ISetupSchema(model.Schema):
65
    """Schema and marker interface
66
    """
67
68
    email_from_sample_publication = Email(
69
        title=_(
70
            "title_senaitesetup_email_from_sample_publication",
71
            default="Publication 'From' address"
72
        ),
73
        description=_(
74
            "description_senaitesetup_email_from_sample_publication",
75
            default="E-mail to use as the 'From' address for outgoing e-mails "
76
                    "when publishing results reports. This address overrides "
77
                    "the value set at portal's 'Mail settings'."
78
        ),
79
        defaultFactory=default_email_from_sample_publication,
80
        required=False,
81
    )
82
83
    directives.widget("email_body_sample_publication", RichTextFieldWidget)
84
    email_body_sample_publication = RichTextField(
85
        title=_("title_senaitesetup_publication_email_text",
86
                default=u"Publication Email Text"),
87
        description=_(
88
            "description_senaitesetup_publication_email_text",
89
            default=u"Set the email body text to be used by default "
90
            "when sending out result reports to the selected recipients. "
91
            "You can use reserved keywords: "
92
            "$client_name, $recipients, $lab_name, $lab_address"),
93
        defaultFactory=default_email_body_sample_publication,
94
        required=False,
95
    )
96
97
    always_cc_responsibles_in_report_emails = schema.Bool(
98
        title=_(
99
            "title_senaitesetup_always_cc_responsibles_in_report_emails",
100
            default=u"Always send publication email to responsibles"),
101
        description=_(
102
            "description_senaitesetup_always_cc_responsibles_in_report_emails",
103
            default="When selected, the responsible persons of all involved "
104
            "lab departments will receive publication emails."
105
        ),
106
        default=True,
107
    )
108
109
    enable_global_auditlog = schema.Bool(
110
        title=_(u"Enable global Auditlog"),
111
        description=_(
112
            "The global Auditlog shows all modifications of the system. "
113
            "When enabled, all entities will be indexed in a separate "
114
            "catalog. This will increase the time when objects are "
115
            "created or modified."
116
        ),
117
        default=False,
118
    )
119
120
    # NOTE:
121
    # We use the `NamedFileFieldWidget` instead of `NamedImageFieldWidget`
122
    # by purpose! Using the latter rises this PIL error (appears only in log):
123
    # IOError: cannot identify image file <cStringIO.StringI object at ...>
124
    directives.widget("site_logo", NamedFileFieldWidget)
125
    site_logo = schema.Bytes(
126
        title=_(u"Site Logo"),
127
        description=_(u"This shows a custom logo on your SENAITE site."),
128
        required=False,
129
    )
130
131
    site_logo_css = schema.ASCII(
132
        title=_(u"Site Logo CSS"),
133
        description=_(
134
            u"Add custom CSS rules for the Logo, "
135
            u"e.g. height:15px; width:150px;"
136
        ),
137
        required=False,
138
    )
139
140
    immediate_results_entry = schema.Bool(
141
        title=_(u"Immediate results entry"),
142
        description=_(
143
            "description_senaitesetup_immediateresultsentry",
144
            default=u"Allow the user to directly enter results after sample "
145
            "creation, e.g. to enter field results immediately, or lab "
146
            "results, when the automatic sample reception is activated."
147
        ),
148
    )
149
150
    categorize_sample_analyses = schema.Bool(
151
        title=_("title_senaitesetup_categorizesampleanalyses",
152
                default=u"Categorize sample analyses"),
153
        description=_(
154
            "description_senaitesetup_categorizesampleanalyses",
155
            default=u"Group analyses by category for samples"
156
        ),
157
        default=False,
158
    )
159
160
    sample_analyses_required = schema.Bool(
161
        title=_("title_senaitesetup_sampleanalysesrequired",
162
                default=u"Require sample analyses"),
163
        description=_(
164
            "description_senaitesetup_sampleanalysesrequired",
165
            default=u"Analyses are required for sample registration"
166
        ),
167
        default=True,
168
    )
169
170
    # Allow Manual Analysis Result Capture Date
171
    allow_manual_result_capture_date = schema.Bool(
172
        title=_("title_senaitesetup_allow_manual_result_capture_date",
173
                default=u"Allow to set the result capture date"),
174
        description=_(
175
            "description_senaitesetup_allow_manual_result_capture_date",
176
            default=u"If this option is activated, the result capture date "
177
                    u"can be entered manually for analyses"),
178
        default=False)
179
180
    max_number_of_samples_add = schema.Int(
181
        title=_(
182
            u"label_senaitesetup_maxnumberofsamplesadd",
183
            default=u"Maximum value for 'Number of samples' field on "
184
                    u"registration"
185
        ),
186
        description=_(
187
            u"description_senaitesetup_maxnumberofsamplesadd",
188
            default=u"Maximum number of samples that can be created in "
189
                    u"accordance with the value set for the field 'Number of "
190
                    u"samples' on the sample registration form"
191
        ),
192
        default=10
193
    )
194
    show_lab_name_in_login = schema.Bool(
195
        title=_(
196
            u"title_senaitesetup_show_lab_name_in_login",
197
            default=u"Display laboratory name in the login page"),
198
        description=_(
199
            u"description_senaitesetup_show_lab_name_in_login",
200
            default=u"When selected, the laboratory name will be displayed"
201
                    u"in the login page, above the access credentials."
202
        ),
203
        default=False,
204
    )
205
206
    ###
207
    # Fieldsets
208
    ###
209
    model.fieldset(
210
        "samples",
211
        label=_("label_senaitesetup_fieldset_samples", default=u"Samples"),
212
        fields=[
213
            "max_number_of_samples_add",
214
        ]
215
    )
216
    model.fieldset(
217
        "analyses",
218
        label=_("label_senaitesetup_fieldset_analyses", default=u"Analyses"),
219
        fields=[
220
            "immediate_results_entry",
221
            "categorize_sample_analyses",
222
            "sample_analyses_required",
223
            "allow_manual_result_capture_date",
224
        ]
225
    )
226
227
    model.fieldset(
228
        "notifications",
229
        label=_(u"Notifications"),
230
        fields=[
231
            "email_from_sample_publication",
232
            "email_body_sample_publication",
233
            "always_cc_responsibles_in_report_emails",
234
        ]
235
    )
236
237
    model.fieldset(
238
        "appearance",
239
        label=_(u"Appearance"),
240
        fields=[
241
            "site_logo",
242
            "site_logo_css",
243
            "show_lab_name_in_login",
244
        ]
245
    )
246
247
248
@implementer(ISetup, ISetupSchema, IHideActionsMenu)
249
class Setup(Container):
250
    """SENAITE Setup Folder
251
    """
252
    security = ClassSecurityInfo()
253
254
    @security.protected(permissions.View)
255
    def getEmailFromSamplePublication(self):
256
        """Returns the 'From' address for publication emails
257
        """
258
        accessor = self.accessor("email_from_sample_publication")
259
        email = accessor(self)
260
        if not email:
261
            email = default_email_from_sample_publication(self)
262
        return email
263
264
    @security.protected(permissions.ModifyPortalContent)
265
    def setEmailFromSamplePublication(self, value):
266
        """Set the 'From' address for publication emails
267
        """
268
        mutator = self.mutator("email_from_sample_publication")
269
        return mutator(self, value)
270
271
    @security.protected(permissions.View)
272
    def getEmailBodySamplePublication(self):
273
        """Returns the transformed email body text for publication emails
274
        """
275
        accessor = self.accessor("email_body_sample_publication")
276
        value = accessor(self)
277
        if IRichTextValue.providedBy(value):
278
            # Transforms the raw value to the output mimetype
279
            value = value.output_relative_to(self)
280
        if not value:
281
            # Always fallback to default value
282
            value = default_email_body_sample_publication(self)
283
        return value
284
285
    @security.protected(permissions.ModifyPortalContent)
286
    def setEmailBodySamplePublication(self, value):
287
        """Set email body text for publication emails
288
        """
289
        mutator = self.mutator("email_body_sample_publication")
290
        return mutator(self, value)
291
292
    @security.protected(permissions.View)
293
    def getAlwaysCCResponsiblesInReportEmail(self):
294
        """Returns if responsibles should always receive publication emails
295
        """
296
        accessor = self.accessor("always_cc_responsibles_in_report_emails")
297
        return accessor(self)
298
299
    @security.protected(permissions.View)
300
    def setAlwaysCCResponsiblesInReportEmail(self, value):
301
        """Set if responsibles should always receive publication emails
302
        """
303
        mutator = self.mutator("always_cc_responsibles_in_report_emails")
304
        return mutator(self, value)
305
306
    @security.protected(permissions.View)
307
    def getEnableGlobalAuditlog(self):
308
        """Returns if the global Auditlog is enabled
309
        """
310
        accessor = self.accessor("enable_global_auditlog")
311
        return accessor(self)
312
313
    @security.protected(permissions.ModifyPortalContent)
314
    def setEnableGlobalAuditlog(self, value):
315
        """Enable/Disable global Auditlogging
316
        """
317
        if value is False:
318
            # clear the auditlog catalog
319
            catalog = api.get_tool(AUDITLOG_CATALOG)
320
            catalog.manage_catalogClear()
321
        mutator = self.mutator("enable_global_auditlog")
322
        return mutator(self, value)
323
324
    @security.protected(permissions.View)
325
    def getSiteLogo(self):
326
        """Returns the global site logo
327
        """
328
        accessor = self.accessor("site_logo")
329
        return accessor(self)
330
331
    @security.protected(permissions.ModifyPortalContent)
332
    def setSiteLogo(self, value):
333
        """Set the site logo
334
        """
335
        mutator = self.mutator("site_logo")
336
        return mutator(self, value)
337
338
    @security.protected(permissions.View)
339
    def getSiteLogoCSS(self):
340
        """Returns the global site logo
341
        """
342
        accessor = self.accessor("site_logo_css")
343
        return accessor(self)
344
345
    @security.protected(permissions.ModifyPortalContent)
346
    def setSiteLogoCSS(self, value):
347
        """Set the site logo
348
        """
349
        mutator = self.mutator("site_logo_css")
350
        return mutator(self, value)
351
352
    @security.protected(permissions.View)
353
    def getImmediateResultsEntry(self):
354
        """Returns if immediate results entry is enabled or not
355
        """
356
        accessor = self.accessor("immediate_results_entry")
357
        return accessor(self)
358
359
    @security.protected(permissions.ModifyPortalContent)
360
    def setImmediateResultsEntry(self, value):
361
        """Enable/Disable global Auditlogging
362
        """
363
        mutator = self.mutator("immediate_results_entry")
364
        return mutator(self, value)
365
366
    @security.protected(permissions.View)
367
    def getCategorizeSampleAnalyses(self):
368
        """Returns if analyses should be grouped by category for samples
369
        """
370
        accessor = self.accessor("categorize_sample_analyses")
371
        return accessor(self)
372
373
    @security.protected(permissions.ModifyPortalContent)
374
    def setCategorizeSampleAnalyses(self, value):
375
        """Enable/Disable grouping of analyses by category for samples
376
        """
377
        mutator = self.mutator("categorize_sample_analyses")
378
        return mutator(self, value)
379
380
    @security.protected(permissions.View)
381
    def getSampleAnalysesRequired(self):
382
        """Returns if analyses are required in sample add form
383
        """
384
        accessor = self.accessor("sample_analyses_required")
385
        return accessor(self)
386
387
    @security.protected(permissions.ModifyPortalContent)
388
    def setSampleAnalysesRequired(self, value):
389
        """Allow/Disallow to create samples without analyses
390
        """
391
        mutator = self.mutator("sample_analyses_required")
392
        return mutator(self, value)
393
394
    @security.protected(permissions.View)
395
    def getAllowManualResultCaptureDate(self):
396
        """Returns if analyses are required in sample add form
397
        """
398
        accessor = self.accessor("allow_manual_result_capture_date")
399
        return accessor(self)
400
401
    @security.protected(permissions.ModifyPortalContent)
402
    def setAllowManualResultCaptureDate(self, value):
403
        """Allow/Disallow to create samples without analyses
404
        """
405
        mutator = self.mutator("allow_manual_result_capture_date")
406
        return mutator(self, value)
407
408
    @security.protected(permissions.View)
409
    def getMaxNumberOfSamplesAdd(self):
410
        """Returns the maximum number of samples that can be created for each
411
        column in sample add form in accordance with the value set for the
412
        field 'Number of samples'
413
        """
414
        accessor = self.accessor("max_number_of_samples_add")
415
        return api.to_int(accessor(self))
416
417
    @security.protected(permissions.ModifyPortalContent)
418
    def setMaxNumberOfSamplesAdd(self, value):
419
        """Sets the maximum number of samples that can be created for each
420
        column in sample add form in accordance with the value set for the
421
        field 'Number of samples'
422
        """
423
        mutator = self.mutator("max_number_of_samples_add")
424
        return mutator(self, value)
425
426
    @security.protected(permissions.View)
427
    def getShowLabNameInLogin(self):
428
        """Returns if the laboratory name has to be displayed in login page
429
        """
430
        accessor = self.accessor("show_lab_name_in_login")
431
        return accessor(self)
432
433
    @security.protected(permissions.ModifyPortalContent)
434
    def setShowLabNameInLogin(self, value):
435
        """Show/hide the laboratory name in the login page
436
        """
437
        mutator = self.mutator("show_lab_name_in_login")
438
        return mutator(self, value)
439