|
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
|
|
|
|