Skip to content

Commit a7aadbd

Browse files
authored
Merge pull request #4039 from stsewd/remove-haystack
Remove haystack from code base
2 parents 43e82b0 + 5d710a4 commit a7aadbd

18 files changed

+9
-565
lines changed

readthedocs/api/base.py

+3-8
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@
2323
from readthedocs.core.utils import trigger_build
2424
from readthedocs.projects.models import ImportedFile, Project
2525

26-
from .utils import PostAuthentication, SearchMixin
26+
from .utils import PostAuthentication
2727

2828
log = logging.getLogger(__name__)
2929

3030

31-
class ProjectResource(ModelResource, SearchMixin):
31+
class ProjectResource(ModelResource):
3232

3333
"""API resource for Project model."""
3434

@@ -139,7 +139,7 @@ def prepend_urls(self):
139139
]
140140

141141

142-
class FileResource(ModelResource, SearchMixin):
142+
class FileResource(ModelResource):
143143

144144
"""API resource for ImportedFile model."""
145145

@@ -152,17 +152,12 @@ class Meta(object):
152152
include_absolute_url = True
153153
authentication = PostAuthentication()
154154
authorization = DjangoAuthorization()
155-
search_facets = ['project']
156155

157156
def prepend_urls(self):
158157
return [
159158
url(
160159
r'^(?P<resource_name>%s)/schema/$' % self._meta.resource_name,
161160
self.wrap_view('get_schema'), name='api_get_schema'),
162-
url(
163-
r'^(?P<resource_name>%s)/search%s$' %
164-
(self._meta.resource_name, trailing_slash()),
165-
self.wrap_view('get_search'), name='api_get_search'),
166161
url(
167162
r'^(?P<resource_name>%s)/anchor%s$' %
168163
(self._meta.resource_name, trailing_slash()),

readthedocs/api/utils.py

+1-137
Original file line numberDiff line numberDiff line change
@@ -1,154 +1,18 @@
11
"""Utility classes for api module"""
22
from __future__ import absolute_import
3-
from builtins import object
43
import logging
54

6-
from django.core.paginator import Paginator, InvalidPage
7-
from django.http import Http404
8-
from django.core.urlresolvers import reverse
95
from django.utils.translation import ugettext
106

11-
from haystack.utils import Highlighter
127
from tastypie.authentication import BasicAuthentication
138
from tastypie.authorization import Authorization
149
from tastypie.resources import ModelResource
15-
from tastypie.exceptions import NotFound, ImmediateHttpResponse
16-
from tastypie import http
17-
from tastypie.utils.mime import build_content_type
10+
from tastypie.exceptions import NotFound
1811

19-
from readthedocs.core.forms import FacetedSearchForm
2012

2113
log = logging.getLogger(__name__)
2214

2315

24-
class SearchMixin(object):
25-
26-
"""
27-
Adds a search api to any ModelResource provided the model is indexed.
28-
29-
The search can be configured using the Meta class in each ModelResource.
30-
The search is limited to the model defined by the meta queryset. If the
31-
search is invalid, a 400 Bad Request will be raised.
32-
33-
e.g.
34-
35-
class Meta:
36-
# Return facet counts for each facetname
37-
search_facets = ['facetname1', 'facetname1']
38-
39-
# Number of results returned per page
40-
search_page_size = 20
41-
42-
# Highlight search terms in the text
43-
search_highlight = True
44-
"""
45-
46-
def get_search(self, request, **kwargs):
47-
self.method_check(request, allowed=['get'])
48-
self.is_authenticated(request)
49-
self.throttle_check(request)
50-
object_list = self._search(
51-
request, self._meta.queryset.model,
52-
facets=getattr(self._meta, 'search_facets', []),
53-
page_size=getattr(self._meta, 'search_page_size', 20),
54-
highlight=getattr(self._meta, 'search_highlight', True))
55-
self.log_throttled_access(request)
56-
return self.create_response(request, object_list)
57-
58-
def _url_template(self, query, selected_facets):
59-
"""
60-
Construct a url template to assist with navigating the resources.
61-
62-
This looks a bit nasty but urllib.urlencode resulted in even
63-
nastier output...
64-
"""
65-
query_params = []
66-
for facet in selected_facets:
67-
query_params.append(('selected_facets', facet))
68-
query_params += [('q', query), ('format', 'json'), ('page', '{0}')]
69-
query_string = '&'.join('='.join(p) for p in query_params)
70-
url_template = reverse('api_get_search', kwargs={
71-
'resource_name': self._meta.resource_name,
72-
'api_name': 'v1'
73-
})
74-
return url_template + '?' + query_string
75-
76-
def _search(self, request, model, facets=None, page_size=20,
77-
highlight=True):
78-
# pylint: disable=too-many-locals
79-
"""
80-
Return a paginated list of objects for a request.
81-
82-
`model`
83-
Limit the search to a particular model
84-
`facets`
85-
A list of facets to include with the results
86-
"""
87-
form = FacetedSearchForm(request.GET, facets=facets or [],
88-
models=(model,), load_all=True)
89-
if not form.is_valid():
90-
return self.error_response({'errors': form.errors}, request)
91-
results = form.search()
92-
93-
paginator = Paginator(results, page_size)
94-
try:
95-
page = paginator.page(int(request.GET.get('page', 1)))
96-
except InvalidPage:
97-
raise Http404(ugettext("Sorry, no results on that page."))
98-
99-
objects = []
100-
query = request.GET.get('q', '')
101-
highlighter = Highlighter(query)
102-
for result in page.object_list:
103-
if not result:
104-
continue
105-
text = result.text
106-
if highlight:
107-
text = highlighter.highlight(text)
108-
bundle = self.build_bundle(obj=result.object, request=request)
109-
bundle = self.full_dehydrate(bundle)
110-
bundle.data['text'] = text
111-
objects.append(bundle)
112-
113-
url_template = self._url_template(query,
114-
form['selected_facets'].value())
115-
page_data = {
116-
'number': page.number,
117-
'per_page': paginator.per_page,
118-
'num_pages': paginator.num_pages,
119-
'page_range': paginator.page_range,
120-
'object_count': paginator.count,
121-
'url_template': url_template,
122-
}
123-
if page.has_next():
124-
page_data['url_next'] = url_template.format(
125-
page.next_page_number())
126-
if page.has_previous():
127-
page_data['url_prev'] = url_template.format(
128-
page.previous_page_number())
129-
130-
object_list = {
131-
'page': page_data,
132-
'objects': objects,
133-
}
134-
if facets:
135-
object_list.update({'facets': results.facet_counts()})
136-
return object_list
137-
138-
# XXX: This method is available in the latest tastypie, remove
139-
# once available in production.
140-
def error_response(self, errors, request):
141-
if request:
142-
desired_format = self.determine_format(request)
143-
else:
144-
desired_format = self._meta.default_format
145-
serialized = self.serialize(request, errors, desired_format)
146-
response = http.HttpBadRequest(
147-
content=serialized,
148-
content_type=build_content_type(desired_format))
149-
raise ImmediateHttpResponse(response=response)
150-
151-
15216
class PostAuthentication(BasicAuthentication):
15317

15418
"""Require HTTP Basic authentication for any method other than GET."""

readthedocs/core/forms.py

-45
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
from django.contrib.auth.models import User
1313
from django.forms.fields import CharField
1414
from django.utils.translation import ugettext_lazy as _
15-
from haystack.forms import SearchForm
16-
from haystack.query import SearchQuerySet
1715

1816
from .models import UserProfile
1917

@@ -88,46 +86,3 @@ def valid_value(self, value):
8886
if ':' not in value:
8987
return False
9088
return True
91-
92-
93-
class FacetedSearchForm(SearchForm):
94-
95-
"""
96-
Supports fetching faceted results with a corresponding query.
97-
98-
`facets`
99-
A list of facet names for which to get facet counts
100-
`models`
101-
Limit the search to one or more models
102-
"""
103-
104-
selected_facets = FacetField(required=False)
105-
106-
def __init__(self, *args, **kwargs):
107-
facets = kwargs.pop('facets', [])
108-
models = kwargs.pop('models', [])
109-
super(FacetedSearchForm, self).__init__(*args, **kwargs)
110-
111-
for facet in facets:
112-
self.searchqueryset = self.searchqueryset.facet(facet)
113-
if models:
114-
self.searchqueryset = self.searchqueryset.models(*models)
115-
116-
def clean_selected_facets(self):
117-
facets = self.cleaned_data['selected_facets']
118-
cleaned_facets = []
119-
clean = SearchQuerySet().query.clean
120-
for facet in facets:
121-
field, value = facet.split(':', 1)
122-
if not value: # Ignore empty values
123-
continue
124-
value = clean(value)
125-
cleaned_facets.append(u'%s:"%s"' % (field, value))
126-
return cleaned_facets
127-
128-
def search(self):
129-
sqs = super(FacetedSearchForm, self).search()
130-
for facet in self.cleaned_data['selected_facets']:
131-
sqs = sqs.narrow(facet)
132-
self.searchqueryset = sqs
133-
return sqs

readthedocs/projects/search_indexes.py

-117
This file was deleted.

readthedocs/settings/base.py

-10
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,6 @@ def INSTALLED_APPS(self): # noqa
8484
'annoying',
8585
'django_extensions',
8686
'messages_extends',
87-
88-
# daniellindsleyrocksdahouse
89-
'haystack',
9087
'tastypie',
9188

9289
# our apps
@@ -313,13 +310,6 @@ def USE_PROMOS(self): # noqa
313310
GROK_API_HOST = 'https://api.grokthedocs.com'
314311
SERVE_DOCS = ['public']
315312

316-
# Haystack
317-
HAYSTACK_CONNECTIONS = {
318-
'default': {
319-
'ENGINE': 'haystack.backends.simple_backend.SimpleEngine',
320-
},
321-
}
322-
323313
# Elasticsearch settings.
324314
ES_HOSTS = ['127.0.0.1:9200']
325315
ES_DEFAULT_NUM_REPLICAS = 0

0 commit comments

Comments
 (0)