combo/combo/apps/search/models.py

169 lines
6.5 KiB
Python
Raw Normal View History

2017-02-22 16:15:37 +01:00
# combo - content management system
# Copyright (C) 2014-2017 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django import template
from django.http import HttpResponse
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.utils.http import quote
from django.template import Context, Template
2017-02-22 16:15:37 +01:00
from jsonfield import JSONField
2017-02-22 16:15:37 +01:00
from combo.utils import requests
from combo.data.models import CellBase
from combo.data.library import register_cell_class
from combo.utils import get_templated_url
from . import engines
2017-02-22 16:15:37 +01:00
@register_cell_class
class SearchCell(CellBase):
template_name = 'combo/search-cell.html'
_search_services = JSONField(_('Search Services'), default=dict, blank=True)
2017-02-22 16:15:37 +01:00
class Meta:
verbose_name = _('Search')
def is_visible(self, user=None):
if not self.search_services:
return False
2017-02-22 16:15:37 +01:00
return super(SearchCell, self).is_visible(user=user)
def get_default_form_class(self):
from .forms import SearchCellForm
return SearchCellForm
2017-02-22 16:15:37 +01:00
@property
def varname(self):
if self.slug:
# no hyphen in varname, could be used in context and templates
return self.slug.replace('-', '_')
return ''
@property
def search_services(self):
services = []
for service_slug in self._search_services.get('data') or []:
service = engines.get(service_slug)
if service and service.get('url'):
service['slug'] = service_slug
services.append(service)
return services
@property
def has_multiple_search_services(self):
return len(self._search_services.get('data') or []) > 1
@classmethod
def get_cells_by_search_service(cls, search_service):
for cell in cls.objects.all():
if search_service in (cell._search_services.get('data') or []):
yield cell
2017-02-22 16:15:37 +01:00
def modify_global_context(self, context, request):
# if self.varname is in the query string (of the page),
# add it to the global context; so it can be used by others cells
# for example by a JsonCell with ...[self.varname]... in its URL
if self.varname and self.varname in request.GET:
context[self.varname] = request.GET.get(self.varname)
def get_cell_extra_context(self, context):
extra_context = super(SearchCell, self).get_cell_extra_context(context)
# if there is a q_<slug> in query_string, send it to the template (to be
# used as an initial query) and remove it from query_string
initial_q = None
initial_query_string = None
if context.get('request'):
request_get = context['request'].GET.copy()
if self.varname and context.get('request'):
q_varname = 'q_%s' % self.varname
if q_varname in request_get:
initial_q = request_get[q_varname]
del request_get[q_varname]
initial_query_string = request_get.urlencode()
extra_context.update({
'initial_q': initial_q,
'initial_query_string': initial_query_string
})
return extra_context
@classmethod
def ajax_results_view(cls, request, cell_pk, service_slug):
2017-02-22 16:15:37 +01:00
cell = cls.objects.get(pk=cell_pk)
if not cell.is_visible(request.user) or not cell.page.is_visible(request.user):
raise PermissionDenied
query = request.GET.get('q')
def render_response(service={}, results={'err': 0, 'data': []}):
template_names = ['combo/search-cell-results.html']
if cell.slug:
template_names.insert(0, 'combo/cells/%s/search-cell-results.html' % cell.slug)
tmpl = template.loader.select_template(template_names)
context = {
'cell': cell,
'results': results,
'search_service': service,
'query': query
}
return HttpResponse(tmpl.render(context, request), content_type='text/html')
for service in cell.search_services:
if service.get('slug') == service_slug:
break
2017-02-22 16:15:37 +01:00
else:
return render_response()
if not query:
return render_response(service)
url = get_templated_url(service['url'],
context={'request': request, 'q': query, 'search_service': service})
url = url % {'q': quote(query.encode('utf-8'))} # if url contains %(q)s
if url.startswith('/'):
url = request.build_absolute_uri(url)
if not url:
return render_response(service)
kwargs = {}
kwargs['cache_duration'] = service.get('cache_duration', 0)
kwargs['remote_service'] = 'auto' if service.get('signature') else None
# don't automatically add user info to query string, if required it can
# be set explicitely in the URL template in the engine definition (via
# {{user_nameid}} or {{user_email}}).
kwargs['without_user'] = True
results = requests.get(url, **kwargs).json()
if service.get('data_key'):
results['data'] = results.get(service['data_key']) or []
hit_templates = {}
if service.get('hit_url_template'):
hit_templates['url'] = Template(service['hit_url_template'])
if service.get('hit_label_template'):
hit_templates['text'] = Template(service['hit_label_template'])
if service.get('hit_description_template'):
hit_templates['description'] = Template(service['hit_description_template'])
if hit_templates:
for hit in results.get('data') or []:
for k, v in hit_templates.items():
hit[k] = v.render(Context(hit))
return render_response(service, results)