0002-search-search-on-pages-and-sub-pages-40224.patch
combo/apps/search/forms.py | ||
---|---|---|
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
from django import forms |
18 |
from django.utils.translation import ugettext_lazy as _ |
|
18 | 19 | |
20 |
from combo.data.models import Page |
|
19 | 21 |
from combo.utils.forms import MultiSortWidget |
20 | 22 | |
21 |
from . import engines |
|
23 |
from . import engines as search_engines
|
|
22 | 24 |
from .models import SearchCell |
23 | 25 | |
24 | 26 | |
... | ... | |
27 | 29 |
model = SearchCell |
28 | 30 |
fields = ('_search_services', 'autofocus') |
29 | 31 | |
32 |
def get_engines(self): |
|
33 |
engines = search_engines.get_engines() |
|
34 |
if '_text' not in engines: |
|
35 |
return engines |
|
36 | ||
37 |
text_engine = engines['_text'] |
|
38 |
for i, page in enumerate(Page.objects.filter(snapshot__isnull=True, sub_slug='', root_for_search=True)): |
|
39 |
engines['_text_page_{}'.format(page.slug)] = { |
|
40 |
'function': text_engine['function'], |
|
41 |
'label': _('Page "%(page)s" and sub pages Contents') % {'page': page.title}, |
|
42 |
} |
|
43 |
return engines |
|
44 | ||
30 | 45 |
def __init__(self, *args, **kwargs): |
31 | 46 |
super(SearchCellForm, self).__init__(*args, **kwargs) |
32 |
options = [(x, engines.get_engines()[x]['label']) for x in engines.get_engines().keys()] |
|
47 |
engines = self.get_engines() |
|
48 |
options = [(x, engines[x]['label']) for x in engines.keys()] |
|
33 | 49 |
self.fields['_search_services'].widget = MultiSortWidget(choices=options, |
34 | 50 |
with_checkboxes=True) |
combo/apps/search/models.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import os |
|
18 | ||
19 | 17 |
from django.contrib.auth.models import Group |
20 | 18 |
from django.contrib.contenttypes import fields |
21 | 19 |
from django.contrib.contenttypes.models import ContentType |
... | ... | |
37 | 35 |
from . import engines |
38 | 36 | |
39 | 37 | |
38 |
def get_root_page_and_children(service_slug): |
|
39 |
if not service_slug.startswith('_text_page_'): |
|
40 |
return |
|
41 | ||
42 |
page_slug = service_slug.replace('_text_page_', '') |
|
43 |
try: |
|
44 |
root_page = Page.objects.get(slug=page_slug, root_for_search=True) |
|
45 |
except (Page.DoesNotExist, Page.MultipleObjectsReturned): |
|
46 |
return |
|
47 | ||
48 |
return root_page.get_descendants_and_me() |
|
49 | ||
50 | ||
40 | 51 |
@register_cell_class |
41 | 52 |
class SearchCell(CellBase): |
42 | 53 |
template_name = 'combo/search-cell.html' |
... | ... | |
69 | 80 |
services = [] |
70 | 81 |
for service_slug in self._search_services.get('data') or []: |
71 | 82 |
service = engines.get(service_slug) |
83 |
if service_slug.startswith('_text_page_'): |
|
84 |
service = engines.get('_text') |
|
72 | 85 |
if service and (service.get('url') or service.get('function')): |
73 | 86 |
service['slug'] = service_slug |
74 | 87 |
services.append(service) |
... | ... | |
142 | 155 |
return render_response(service) |
143 | 156 | |
144 | 157 |
if service.get('function'): # internal search engine |
145 |
results = {'data': service['function'](request, query)} |
|
158 |
pages = get_root_page_and_children(service_slug) |
|
159 |
results = {'data': service['function'](request, query, pages=pages)} |
|
146 | 160 |
else: |
147 |
url = get_templated_url(service['url'], |
|
148 |
context={'request': request, 'q': query, 'search_service': service}) |
|
161 |
url = get_templated_url( |
|
162 |
service['url'], |
|
163 |
context={'request': request, 'q': query, 'search_service': service}) |
|
149 | 164 |
url = url % {'q': quote(query.encode('utf-8'))} # if url contains %(q)s |
150 | 165 |
if url.startswith('/'): |
151 | 166 |
url = request.build_absolute_uri(url) |
... | ... | |
186 | 201 |
return render_response(service, results) |
187 | 202 | |
188 | 203 |
def has_text_search_service(self): |
189 |
return '_text' in self._search_services.get('data', [])
|
|
204 |
return any(key.startswith('_text') for key in self._search_services.get('data', []))
|
|
190 | 205 | |
191 | 206 |
def missing_index(self): |
192 | 207 |
return IndexedCell.objects.all().count() == 0 |
combo/apps/search/utils.py | ||
---|---|---|
77 | 77 |
indexed_cell.save() |
78 | 78 | |
79 | 79 | |
80 |
def search_site(request, query): |
|
80 |
def search_site(request, query, pages=None): |
|
81 |
pages = pages or [] |
|
82 | ||
81 | 83 |
if connection.vendor == 'postgresql': |
82 | 84 |
config = settings.POSTGRESQL_FTS_SEARCH_CONFIG |
83 | 85 |
vector = SearchVector('title', config=config, weight='A') + SearchVector('indexed_text', config=config, weight='A') |
... | ... | |
94 | 96 |
Q(restricted_groups__in=request.user.groups.all())) |
95 | 97 |
qs = qs.exclude(excluded_groups__in=request.user.groups.all()) |
96 | 98 | |
99 |
if pages: |
|
100 |
qs = qs.filter(page__in=pages) |
|
101 | ||
97 | 102 |
hits = [] |
98 | 103 |
seen = {} |
99 | 104 |
for hit in qs: |
tests/test_search.py | ||
---|---|---|
309 | 309 |
assert resp.text.count('<li') == 0 |
310 | 310 | |
311 | 311 | |
312 |
def test_search_on_root_page_api(app): |
|
313 |
page = Page.objects.create(title='example page', slug='example-page') |
|
314 |
TextCell.objects.create(page=page, text='<p>foobar baz</p>', order=0) |
|
315 | ||
316 |
second_page = Page.objects.create(title='second page', slug='second-page', root_for_search=True) |
|
317 |
TextCell.objects.create(page=second_page, text='<p>other baz</p>', order=0) |
|
318 | ||
319 |
index_site() |
|
320 | ||
321 |
cell = SearchCell.objects.create(page=page, _search_services={'data': ['_text']}, order=0) |
|
322 |
resp = app.get('/ajax/search/%s/_text/?q=baz' % cell.pk, status=200) |
|
323 |
assert resp.text.count('<li') == 2 |
|
324 | ||
325 |
cell._search_services = {'data': ['_text_page_second-page']} |
|
326 |
cell.save() |
|
327 |
resp = app.get('/ajax/search/%s/_text_page_second-page/?q=baz' % cell.pk, status=200) |
|
328 |
assert resp.text.count('<li') == 1 |
|
329 | ||
330 |
# invalid page (not root for search), search everywhere |
|
331 |
cell._search_services = {'data': ['_text_page_example-page']} |
|
332 |
cell.save() |
|
333 |
resp = app.get('/ajax/search/%s/_text_page_example-page/?q=baz' % cell.pk, status=200) |
|
334 |
assert resp.text.count('<li') == 2 |
|
335 | ||
336 |
# page does not exists, search everywhere |
|
337 |
cell._search_services = {'data': ['_text_page_foo']} |
|
338 |
cell.save() |
|
339 |
resp = app.get('/ajax/search/%s/_text_page_foo/?q=baz' % cell.pk, status=200) |
|
340 |
assert resp.text.count('<li') == 2 |
|
341 | ||
342 |
# slug is not unique, search everywhere |
|
343 |
page.slug = 'second-page' |
|
344 |
page.root_for_search = True |
|
345 |
page.save() |
|
346 |
cell._search_services = {'data': ['_text_page_second-page']} |
|
347 |
cell.save() |
|
348 |
resp = app.get('/ajax/search/%s/_text_page_second-page/?q=baz' % cell.pk, status=200) |
|
349 |
assert resp.text.count('<li') == 2 |
|
350 | ||
351 | ||
312 | 352 |
def test_search_external_links(app): |
313 | 353 |
page = Page(title='example page', slug='example-page') |
314 | 354 |
page.save() |
315 |
- |