Passed
Push — master ( f42093...226b52 )
by Ramon
04:24
created

Attachment.getClientUID()   A

Complexity

Conditions 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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