Passed
Push — master ( 9907a0...5c2062 )
by Ramon
04:59
created

DefaultReferenceWidgetVocabulary.__init__()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nop 3
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 json
9
10
from zope.interface import implements
11
12
from Products.AdvancedQuery import Or, MatchRegexp, Generic
13
from Products.CMFCore.utils import getToolByName
14
15
from bika.lims.utils import to_utf8 as _c
16
from bika.lims.utils import to_unicode as _u
17
from bika.lims.interfaces import IReferenceWidgetVocabulary
18
19
20
class DefaultReferenceWidgetVocabulary(object):
21
    implements(IReferenceWidgetVocabulary)
22
23
    def __init__(self, context, request):
24
        self.context = context
25
        self.request = request
26
27
    def __call__(self, result=None, specification=None, **kwargs):
28
        searchTerm = _c(self.request.get('searchTerm', '')).lower()
29
        force_all = self.request.get('force_all', 'false')
30
        searchFields = 'search_fields' in self.request \
31
            and json.loads(_u(self.request.get('search_fields', '[]'))) \
32
            or ('Title',)
33
        # lookup objects from ZODB
34
        catalog_name = _c(self.request.get('catalog_name', 'portal_catalog'))
35
        catalog = getToolByName(self.context, catalog_name)
36
37
        # json.loads does unicode conversion, which will fail in the catalog
38
        # search for some cases. So we need to convert the strings to utf8
39
        # see: https://github.com/senaite/bika.lims/issues/443
40
        base_query = json.loads(self.request['base_query'])
41
        search_query = json.loads(self.request.get('search_query', "{}"))
42
        base_query = self.to_utf8(base_query)
43
        search_query = self.to_utf8(search_query)
44
45
        # first with all queries
46
        contentFilter = dict((k, v) for k, v in base_query.items())
47
        contentFilter.update(search_query)
48
49
        # Sorted by? (by default, Title)
50
        sort_on = self.request.get('sidx', 'Title')
51
        if sort_on == 'Title':
52
            sort_on = 'sortable_title'
53
        if sort_on:
54
            # Check if is an index and if is sortable. Otherwise, assume the
55
            # sorting must be done manually
56
            index = catalog.Indexes.get(sort_on, None)
57
            if index and index.meta_type in ['FieldIndex', 'DateIndex']:
58
                contentFilter['sort_on'] = sort_on
59
                # Sort order?
60
                sort_order = self.request.get('sord', 'asc')
61
                if (sort_order in ['desc', 'reverse', 'rev', 'descending']):
62
                    contentFilter['sort_order'] = 'descending'
63
                else:
64
                    contentFilter['sort_order'] = 'ascending'
65
66
        # Can do a search for indexes?
67
        criterias = []
68
        fields_wo_index = []
69
        if searchTerm:
70
            for field_name in searchFields:
71
                index = catalog.Indexes.get(field_name, None)
72
                if not index:
73
                    fields_wo_index.append(field_name)
74
                    continue
75
                if index.meta_type in ('ZCTextIndex'):
76
                    if searchTerm.isspace():
77
                        # earchTerm != ' ' added because of
78
                        # https://github.com/plone/Products.CMFPlone/issues
79
                        # /1537
80
                        searchTerm = ''
81
                        continue
82
                    else:
83
                        temp_st = searchTerm + '*'
84
                        criterias.append(MatchRegexp(field_name, temp_st))
85
                elif index.meta_type in ('FieldIndex'):
86
                    criterias.append(MatchRegexp(field_name, searchTerm))
87
                elif index.meta_type == 'DateIndex':
88
                    msg = "Unhandled DateIndex search on '%s'" % field_name
89
                    from bika.lims import logger
90
                    logger.warn(msg)
91
                else:
92
                    criterias.append(Generic(field_name, searchTerm))
93
94
        if criterias:
95
            # Advanced search
96
            advanced_query = catalog.makeAdvancedQuery(contentFilter)
97
            aq_or = Or()
98
            for criteria in criterias:
99
                aq_or.addSubquery(criteria)
100
            advanced_query &= aq_or
101
            brains = catalog.evalAdvancedQuery(advanced_query)
102
        else:
103
            brains = catalog(contentFilter)
104
105
        if brains and searchTerm and fields_wo_index:
106
            _brains = []
107
            for brain in brains:
108
                for field_name in fields_wo_index:
109
                    value = getattr(brain, field_name, None)
110
                    if not value:
111
                        instance = brain.getObject()
112
                        schema = instance.Schema()
113
                        if field_name in schema:
114
                            value = schema[field_name].get(instance)
115
                    if callable(value):
116
                        value = value()
117
                    if value and value.lower().find(searchTerm) > -1:
118
                        _brains.append(brain)
119
                        break
120
            brains = _brains
121
122
        # Then just base_query alone ("show all if no match")
123
        if not brains and force_all.lower() == 'true':
124
            if search_query:
125
                brains = catalog(base_query)
126
                if brains and searchTerm:
127
                    _brains = [p for p in brains
128
                               if p.Title.lower().find(searchTerm) > -1]
129
                    if _brains:
130
                        brains = _brains
131
132
        return brains
133
134
    def to_utf8(self, data):
135
        """
136
        Convert unicode values to strings even if they belong to lists or dicts.
137
        :param data: an object.
138
        :return: The object with all unicode values converted to string.
139
        """
140
        # if this is a unicode string, return its string representation
141
        if isinstance(data, unicode):
142
            return data.encode('utf-8')
143
144
        # if this is a list of values, return list of string values
145
        if isinstance(data, list):
146
            return [self.to_utf8(item) for item in data]
147
148
        # if this is a dictionary, return dictionary of string keys and values
149
        if isinstance(data, dict):
150
            return {
151
                self.to_utf8(key): self.to_utf8(value)
152
                for key, value in data.iteritems()
153
            }
154
            # if it's anything else, return it in its original form
155
156
        return data
157