Passed
Push — 2.x ( 979cdb...fa02b5 )
by Jordi
07:13
created

DefaultReferenceWidgetVocabulary.query()   C

Complexity

Conditions 9

Size

Total Lines 35
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 27
dl 0
loc 35
rs 6.6666
c 0
b 0
f 0
cc 9
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 bika.lims import api
22
from bika.lims import logger
23
from bika.lims.interfaces import IReferenceWidgetVocabulary
24
from bika.lims.utils import get_client
25
from Products.ZCTextIndex.ParseTree import ParseError
26
from zope.interface import implements
27
28
ALLOWED_QUERY_KEYS = [
29
    "sort_on",
30
    "sort_order",
31
]
32
33
# Search index placeholder for dynamic lookup by the search endpoint
34
SEARCH_INDEX_MARKER = "__search__"
35
36
# default search index name
37
DEFAULT_SEARCH_INDEXES = [
38
    "listing_searchable_text",
39
    "Title",
40
]
41
42
43
class DefaultReferenceWidgetVocabulary(object):
44
    implements(IReferenceWidgetVocabulary)
45
46
    def __init__(self, context, request):
47
        self.context = context
48
        self.request = request
49
50
    @property
51
    def catalog_name(self):
52
        """Returns the catalog name to be used for the search
53
        """
54
        return self.request.get("catalog")
55
56
    @property
57
    def query(self):
58
        """Build the raw request from the query params
59
        """
60
        catalog = api.get_tool(self.catalog_name)
61
        indexes = catalog.indexes()
62
        raw_query = dict(self.request.form.items())
63
64
        query = {}
65
        for key, value in raw_query.items():
66
            if key in ALLOWED_QUERY_KEYS:
67
                query[key] = value
68
            elif key in indexes:
69
                query[key] = value
70
            elif key == SEARCH_INDEX_MARKER:
71
                # find a suitable ZCText index for the search
72
                can_search = False
73
                search_indexes = DEFAULT_SEARCH_INDEXES[:]
74
                # check if we have a content specific search index
75
                portal_type = raw_query.get("portal_type")
76
                if api.is_string(portal_type):
77
                    index = "{}_searchable_text".format(portal_type.lower())
78
                    search_indexes.insert(0, index)
79
                # check if one of the search indexes match
80
                for index in search_indexes:
81
                    if index in indexes:
82
                        query[index] = value
83
                        can_search = True
84
                        break
85
                if not can_search:
86
                    logger.warn("No text index found for query '%s'!" % value)
87
            else:
88
                # skip unknown indexes for the query
89
                continue
90
        return query
91
92
    def get_raw_query(self):
93
        """BBB
94
        """
95
        return self.query
96
97
    def __call__(self):
98
        # Get the raw query to use
99
        # Raw query is built from base query baseline, including additional
100
        # parameters defined in the request and the search query as well
101
        query = self.get_raw_query()
102
        if not query:
103
            return []
104
105
        # Do the search
106
        logger.info("Reference Widget Raw Query for catalog {}: {}"
107
                    .format(self.catalog_name, repr(query)))
108
        try:
109
            brains = api.search(query, self.catalog_name)
110
        except ParseError:
111
            brains = []
112
        logger.info("Found {} results".format(len(brains)))
113
        return brains
114
115
116
class ClientAwareReferenceWidgetVocabulary(DefaultReferenceWidgetVocabulary):
117
    """Injects search criteria (filters) in the query when the current context
118
    is, belongs or is associated to a Client
119
    """
120
121
    # portal_types that might be bound to a client
122
    client_bound_types = [
123
        "Contact",
124
        "Batch",
125
        "AnalysisProfile",
126
        "AnalysisSpec",
127
        "ARTemplate",
128
        "SamplePoint"
129
    ]
130
131
    def get_raw_query(self):
132
        """Returns the raw query to use for current search, based on the
133
        base query + update query
134
        """
135
        query = super(
136
            ClientAwareReferenceWidgetVocabulary, self).get_raw_query()
137
138
        if self.is_client_aware(query):
139
140
            client = get_client(self.context)
141
            client_uid = client and api.get_uid(client) or None
142
143
            if client_uid:
144
                # Apply the search criteria for this client
145
                if "Contact" in self.get_portal_types(query):
146
                    query["getParentUID"] = [client_uid]
147
                else:
148
                    query["getClientUID"] = [client_uid, ""]
149
150
        return query
151
152
    def is_client_aware(self, query):
153
        """Returns whether the query passed in requires a filter by client
154
        """
155
        portal_types = self.get_portal_types(query)
156
        intersect = set(portal_types).intersection(self.client_bound_types)
157
        return len(intersect) > 0
158
159
    def get_portal_types(self, query):
160
        """Return the list of portal types from the query passed-in
161
        """
162
        portal_types = query.get("portal_type", [])
163
        if api.is_string(portal_types):
164
            portal_types = [portal_types]
165
        return portal_types
166