Passed
Push — master ( d6acae...068107 )
by Ramon
04:47
created

ajaxReferenceWidgetSearch.get_field_names()   A

Complexity

Conditions 5

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.3333
c 0
b 0
f 0
cc 5
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-2020 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
import json
22
23
import plone
24
from AccessControl import ClassSecurityInfo
25
from Products.Archetypes.Registry import registerWidget
26
from Products.Archetypes.Widget import StringWidget
27
from bika.lims import api
28
from bika.lims import logger
29
from bika.lims import bikaMessageFactory as _
30
from bika.lims.browser import BrowserView
31
from bika.lims.interfaces import IReferenceWidgetVocabulary
32
from bika.lims.utils import to_unicode as _u
33
from senaite.core.supermodel.model import SuperModel
34
from zope.component import getAdapters
35
from plone import protect
36
37
38
class ReferenceWidget(StringWidget):
39
    _properties = StringWidget._properties.copy()
40
    _properties.update({
41
        'macro': "bika_widgets/referencewidget",
42
        'helper_js': ("bika_widgets/referencewidget.js",),
43
        'helper_css': ("bika_widgets/referencewidget.css",),
44
45
        'url': 'referencewidget_search',
46
        'catalog_name': 'portal_catalog',
47
48
        # base_query can be a dict or a callable returning a dict
49
        'base_query': {},
50
51
        # This will be faster if the columnNames are catalog indexes
52
        'colModel': [
53
            {'columnName': 'Title', 'width': '30', 'label': _(
54
                'Title'), 'align': 'left'},
55
            {'columnName': 'Description', 'width': '70', 'label': _(
56
                'Description'), 'align': 'left'},
57
            # UID is required in colModel
58
            {'columnName': 'UID', 'hidden': True},
59
        ],
60
61
        # Default field to put back into input elements
62
        'ui_item': 'Title',
63
        'search_fields': ('Title',),
64
        'discard_empty': [],
65
        'popup_width': '550px',
66
        'showOn': False,
67
        'searchIcon': True,
68
        'minLength': '0',
69
        'delay': '500',
70
        'resetButton': False,
71
        'sord': 'asc',
72
        'sidx': 'Title',
73
        'force_all': False,
74
        'portal_types': {},
75
    })
76
    security = ClassSecurityInfo()
77
78
    security.declarePublic('process_form')
79
80
    def process_form(self, instance, field, form, empty_marker=None,
81
                     emptyReturnsMarker=False):
82
        """Return a UID so that ReferenceField understands.
83
        """
84
        fieldName = field.getName()
85
        if fieldName + "_uid" in form:
86
            uid = form.get(fieldName + "_uid", '')
87
            if field.multiValued and\
88
                    (isinstance(uid, str) or isinstance(uid, unicode)):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable unicode does not seem to be defined.
Loading history...
89
                uid = uid.split(",")
90
        elif fieldName in form:
91
            uid = form.get(fieldName, '')
92
            if field.multiValued and\
93
                    (isinstance(uid, str) or isinstance(uid, unicode)):
94
                uid = uid.split(",")
95
        else:
96
            uid = None
97
        return uid, {}
98
99
    def get_combogrid_options(self, context, fieldName):
100
        colModel = self.colModel
101
        if 'UID' not in [x['columnName'] for x in colModel]:
102
            colModel.append({'columnName': 'UID', 'hidden': True})
103
        options = {
104
            'url': self.url,
105
            'colModel': colModel,
106
            'showOn': self.showOn,
107
            'width': self.popup_width,
108
            'sord': self.sord,
109
            'sidx': self.sidx,
110
            'force_all': self.force_all,
111
            'search_fields': self.search_fields,
112
            'discard_empty': self.discard_empty,
113
            'minLength': self.minLength,
114
            'resetButton': self.resetButton,
115
            'searchIcon': self.searchIcon,
116
            'delay': self.delay,
117
        }
118
        return json.dumps(options)
119
120
    def get_base_query(self, context, fieldName):
121
        base_query = self.base_query
122
        if callable(base_query):
123
            base_query = base_query()
124
        if base_query and isinstance(base_query, basestring):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable basestring does not seem to be defined.
Loading history...
125
            base_query = json.loads(base_query)
126
127
        # portal_type: use field allowed types
128
        field = context.Schema().getField(fieldName)
129
        allowed_types = getattr(field, 'allowed_types', None)
130
        allowed_types_method = getattr(field, 'allowed_types_method', None)
131
        if allowed_types_method:
132
            meth = getattr(context, allowed_types_method)
133
            allowed_types = meth(field)
134
        # If field has no allowed_types defined, use widget's portal_type prop
135
        base_query['portal_type'] = allowed_types \
136
            if allowed_types \
137
            else self.portal_types
138
139
        return json.dumps(self.base_query)
140
141
    def initial_uid_field_value(self, value):
142
        if type(value) in (list, tuple):
143
            ret = ",".join([v.UID() for v in value])
144
        elif type(value) in [str, ]:
145
            ret = value
146
        else:
147
            ret = value.UID() if value else value
148
        return ret
149
150
151
registerWidget(ReferenceWidget, title='Reference Widget')
152
153
class ajaxReferenceWidgetSearch(BrowserView):
154
    """ Source for jquery combo dropdown box
155
    """
156
157
    @property
158
    def num_page(self):
159
        """Returns the number of page to render
160
        """
161
        return api.to_int(self.request.get("page", None), default=1)
162
163
    @property
164
    def num_rows_page(self):
165
        """Returns the number of rows per page to render
166
        """
167
        return api.to_int(self.request.get("rows", None), default=10)
168
169
    def get_field_names(self):
170
        """Return the field names to get values for
171
        """
172
        col_model = self.request.get("colModel", None)
173
        if not col_model:
174
            return ["UID",]
175
176
        names = []
177
        col_model = json.loads(_u(col_model))
178
        if isinstance(col_model, (list, tuple)):
179
            names = map(lambda c: c.get("columnName", "").strip(), col_model)
180
181
        # UID is used by reference widget to know the object that the user
182
        # selected from the popup list
183
        if "UID" not in names:
184
            names.append("UID")
185
186
        return filter(None, names)
187
188
    def get_data_record(self, brain, field_names):
189
        """Returns a dict with the column values for the given brain
190
        """
191
        record = {}
192
        model = None
193
194
        for field_name in field_names:
195
            # First try to get the value directly from the brain
196
            value = getattr(brain, field_name, None)
197
198
            # No metadata for this column name
199
            if value is None:
200
                logger.warn("Not a metadata field: {}".format(field_name))
201
                model = model or SuperModel(brain)
202
                value = model.get(field_name, None)
203
                if callable(value):
204
                    value = value()
205
206
            # ' ' instead of '' because empty div fields don't render
207
            # correctly in combo results table
208
            record[field_name] = value or " "
209
210
        return record
211
212
    def search(self):
213
        """Returns the list of brains that match with the request criteria
214
        """
215
        brains = []
216
        # TODO Legacy
217
        for name, adapter in getAdapters((self.context, self.request),
218
                                         IReferenceWidgetVocabulary):
219
            brains.extend(adapter())
220
        return brains
221
222
    def to_data_rows(self, brains):
223
        """Returns a list of dictionaries representing the values of each brain
224
        """
225
        fields = self.get_field_names()
226
        return map(lambda brain: self.get_data_record(brain, fields), brains)
227
228
    def to_json_payload(self, data_rows):
229
        """Returns the json payload
230
        """
231
        num_rows = len(data_rows)
232
        num_page = self.num_page
233
        num_rows_page = self.num_rows_page
234
235
        pages = num_rows / num_rows_page
236
        pages += divmod(num_rows, num_rows_page)[1] and 1 or 0
237
        start = (num_page - 1) * num_rows_page
238
        end = num_page * num_rows_page
239
        payload = {"page": num_page,
240
                   "total": pages,
241
                   "records": num_rows,
242
                   "rows": data_rows[start:end]}
243
        return json.dumps(payload)
244
245
    def __call__(self):
246
        protect.CheckAuthenticator(self.request)
247
248
        # Do the search
249
        brains = self.search()
250
251
        # Generate the data rows to display
252
        data_rows = self.to_data_rows(brains)
253
254
        # Return the payload
255
        return self.to_json_payload(data_rows)
256