Passed
Push — 2.x ( 9e4ac8...65b15a )
by Jordi
06:32
created

Attachment.getAnalysis()   A

Complexity

Conditions 3

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 18
rs 9.85
c 0
b 0
f 0
cc 3
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-2021 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 bika.lims import bikaMessageFactory as _
24
from bika.lims import deprecated
25
from bika.lims import logger
26
from bika.lims.browser.fields import UIDReferenceField
27
from bika.lims.browser.fields.uidreferencefield import get_backreferences
28
from bika.lims.browser.widgets import DateTimeWidget
29
from bika.lims.browser.widgets import ReferenceWidget
30
from bika.lims.config import PROJECTNAME
31
from bika.lims.content.bikaschema import BikaSchema
32
from bika.lims.content.clientawaremixin import ClientAwareMixin
33
from bika.lims.interfaces.analysis import IRequestAnalysis
34
from DateTime import DateTime
35
from plone.app.blob.field import FileField
36
from Products.Archetypes.atapi import BaseFolder
37
from Products.Archetypes.atapi import BooleanField
38
from Products.Archetypes.atapi import BooleanWidget
39
from Products.Archetypes.atapi import DateTimeField
40
from Products.Archetypes.atapi import FileWidget
41
from Products.Archetypes.atapi import Schema
42
from Products.Archetypes.atapi import StringField
43
from Products.Archetypes.atapi import StringWidget
44
from Products.Archetypes.atapi import registerType
45
46
schema = BikaSchema.copy() + Schema((
47
48
    FileField(
49
        "AttachmentFile",
50
        widget=FileWidget(
51
            label=_("Attachment"),
52
        ),
53
    ),
54
55
    UIDReferenceField(
56
        "AttachmentType",
57
        required=0,
58
        allowed_types=("AttachmentType",),
59
        widget=ReferenceWidget(
60
            label=_("Attachment Type"),
61
        ),
62
    ),
63
64
    BooleanField(
65
        "RenderInReport",
66
        default=True,
67
        widget=BooleanWidget(
68
            label=_("Render attachment in report"),
69
            description=_(
70
                "Only images can be rendered in the report directly"),
71
            visible=True,
72
        ),
73
    ),
74
75
    StringField(
76
        "AttachmentKeys",
77
        searchable=True,
78
        widget=StringWidget(
79
            label=_("Attachment Keys"),
80
        ),
81
    ),
82
83
    DateTimeField(
84
        "DateLoaded",
85
        required=1,
86
        default_method="current_date",
87
        widget=DateTimeWidget(
88
            label=_("Date Loaded"),
89
        ),
90
    ),
91
))
92
93
schema["id"].required = False
94
schema["title"].required = False
95
96
97
class Attachment(BaseFolder, ClientAwareMixin):
98
    """Attachments are stored per client and can be linked to ARs or Analyses
99
    """
100
    security = ClassSecurityInfo()
101
    displayContentsTab = False
102
    schema = schema
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable schema does not seem to be defined.
Loading history...
103
    _at_rename_after_creation = True
104
105
    def _renameAfterCreation(self, check_auto_id=False):
106
        """Rename with the IDServer
107
        """
108
        from bika.lims.idserver import renameAfterCreation
109
        renameAfterCreation(self)
110
111
    @security.public
112
    def Title(self):
113
        """Return the ID
114
115
        :returns: IDServer generated ID
116
        """
117
        return self.getId()
118
119
    @security.public
120
    def getRequestID(self):
121
        """Return the ID of the linked AR
122
        """
123
        ar = self.getRequest()
124
        if not ar:
125
            return ""
126
        return api.get_id(ar)
127
128
    @security.public
129
    def getAttachmentTypeUID(self):
130
        """Return the UID of the assigned attachment type
131
        """
132
        attachment_type = self.getAttachmentType()
133
        if not attachment_type:
134
            return ""
135
        return api.get_uid(attachment_type)
136
137
    @security.public
138
    def getLinkedRequests(self):
139
        """Lookup linked Analysis Requests
140
141
        :returns: sorted list of ARs, where the latest AR comes first
142
        """
143
        uids = get_backreferences(self, "AnalysisRequestAttachment")
144
        # fetch the objects by UID and handle nonexisting UIDs gracefully
145
        uc = api.get_tool("uid_catalog")
146
        ars = [api.get_object(brain) for brain in uc(UID=uids)]
147
        # sort by physical path, so that attachments coming from an AR with a
148
        # higher "-Rn" suffix get sorted correctly.
149
        # N.B. the created date is the same, hence we can not use it
150
        return sorted(ars, key=api.get_path, reverse=True)
151
152
    @security.public
153
    def getLinkedAnalyses(self):
154
        """Lookup linked Analyses
155
156
        :returns: sorted list of ANs, where the latest AN comes first
157
        """
158
        # Fetch the linked Analyses UIDs
159
        uids = get_backreferences(self, "AnalysisAttachment")
160
        # fetch the objects by UID and handle nonexisting UIDs gracefully
161
        uc = api.get_tool("uid_catalog")
162
        ans = [api.get_object(brain) for brain in uc(UID=uids)]
163
        # sort by physical path, so that attachments coming from an AR with a
164
        # higher "-Rn" suffix get sorted correctly.
165
        # N.B. the created date is the same, hence we can not use it
166
        return sorted(ans, key=api.get_path, reverse=True)
167
168
    @security.public
169
    def getTextTitle(self):
170
        """Return a title for texts and listings
171
        """
172
        request_id = self.getRequestID()
173
        if not request_id:
174
            return ""
175
176
        analysis = self.getAnalysis()
177
        if not analysis:
178
            return request_id
179
180
        return "%s - %s" % (request_id, analysis.Title())
181
182
    @security.public
183
    def getRequest(self):
184
        """Return the primary AR this attachment is linked
185
        """
186
        ars = self.getLinkedRequests()
187
188
        if len(ars) > 1:
189
            # Attachment is assigned to more than one Analysis Request.
190
            # This might happen when the AR was invalidated
191
            ar_ids = ", ".join(map(api.get_id, ars))
192
            logger.info("Attachment assigned to more than one AR: [{}]. "
193
                        "The first AR will be returned".format(ar_ids))
194
195
        # return the first AR
196
        if len(ars) >= 1:
197
            return ars[0]
198
199
        # Check if the attachment is linked to an analysis and try to get the
200
        # AR from the linked analysis
201
        analysis = self.getAnalysis()
202
        if IRequestAnalysis.providedBy(analysis):
203
            return analysis.getRequest()
204
205
        return None
206
207
    @security.public
208
    def getAnalysis(self):
209
        """Return the primary analysis this attachment is linked
210
        """
211
        analysis = None
212
        ans = self.getLinkedAnalyses()
213
214
        if len(ans) > 1:
215
            # Attachment is assigned to more than one Analysis. This might
216
            # happen when the AR was invalidated
217
            an_ids = ", ".join(map(api.get_id, ans))
218
            logger.info("Attachment assigned to more than one Analysis: [{}]. "
219
                        "The first Analysis will be returned".format(an_ids))
220
221
        if len(ans) >= 1:
222
            analysis = ans[0]
223
224
        return analysis
225
226
    @security.public
227
    def current_date(self):
228
        """Return current date
229
        """
230
        return DateTime()
231
232
    @security.public
233
    def getFilename(self):
234
        """Returns the filename of the underlying File object
235
        """
236
        att_file = self.getAttachmentFile()
237
        return att_file.filename
238
239
    @deprecated(comment="Removed in 3.x", replacement="getRenderInReport")
240
    def getReportOption(self):
241
        """BBB: returns "r" if RenderInReport is True, otherwise "i"
242
        """
243
        if not self.getRenderInReport():
244
            return "i"
245
        return "r"
246
247
    @deprecated(comment="Removed in 3.x", replacement="setRenderInReport")
248
    def setReportOption(self, value):
249
        """BBB: set RenderInReport to True if the passed in value is "r",
250
                and for all other values to False
251
        """
252
        self.setRenderInReport(value == "r")
253
254
255
registerType(Attachment, PROJECTNAME)
256