|
1 | 1 | """Utility classes for api module"""
|
2 | 2 | from __future__ import absolute_import
|
3 |
| -from builtins import object |
4 | 3 | import logging
|
5 | 4 |
|
6 |
| -from django.core.paginator import Paginator, InvalidPage |
7 |
| -from django.http import Http404 |
8 |
| -from django.core.urlresolvers import reverse |
9 | 5 | from django.utils.translation import ugettext
|
10 | 6 |
|
11 |
| -from haystack.utils import Highlighter |
12 | 7 | from tastypie.authentication import BasicAuthentication
|
13 | 8 | from tastypie.authorization import Authorization
|
14 | 9 | 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 |
18 | 11 |
|
19 |
| -from readthedocs.core.forms import FacetedSearchForm |
20 | 12 |
|
21 | 13 | log = logging.getLogger(__name__)
|
22 | 14 |
|
23 | 15 |
|
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 |
| - |
152 | 16 | class PostAuthentication(BasicAuthentication):
|
153 | 17 |
|
154 | 18 | """Require HTTP Basic authentication for any method other than GET."""
|
|
0 commit comments