# -*- coding: utf-8 -*-
"""
Base ViewSets.
"""
from __future__ import absolute_import, unicode_literals
from django.http import Http404
from django.core.exceptions import ImproperlyConfigured
from elasticsearch_dsl import Search
from elasticsearch_dsl.connections import connections
from rest_framework.decorators import list_route
from rest_framework.response import Response
from rest_framework import status
from rest_framework.viewsets import ReadOnlyModelViewSet
from .pagination import PageNumberPagination
from .utils import DictionaryProxy
__title__ = 'django_elasticsearch_dsl_drf.viewsets'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2017-2018 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = (
'BaseDocumentViewSet',
'DocumentViewSet',
'FunctionalSuggestMixin',
'SuggestMixin',
)
[docs]class SuggestMixin(object):
"""Suggest mixin."""
[docs] @list_route()
def suggest(self, request):
"""Suggest functionality."""
queryset = self.filter_queryset(self.get_queryset())
is_suggest = getattr(queryset, '_suggest', False)
if not is_suggest:
return Response(
status=status.HTTP_400_BAD_REQUEST
)
page = self.paginate_queryset(queryset)
return Response(page)
[docs]class FunctionalSuggestMixin(object):
"""Functional suggest mixin."""
[docs] @list_route()
def functional_suggest(self, request):
"""Functional suggest functionality."""
# TODO: leave or remove?
if 'view' in request.parser_context:
view = request.parser_context['view']
filter_backend_names = [
__b.__name__
for __b
in view.filter_backends
]
if 'FunctionalSuggesterFilterBackend' not in filter_backend_names:
raise ImproperlyConfigured(
"To use functional suggester backend you shall add "
"`FunctionalSuggesterFilterBackend` to the "
"`filter_backends` of your ViewSet."
)
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
return Response(page)
[docs]class BaseDocumentViewSet(ReadOnlyModelViewSet):
"""Base document ViewSet."""
document_uid_field = 'id'
document = None # Re-define
pagination_class = PageNumberPagination
def __init__(self, *args, **kwargs):
assert self.document is not None
self.client = connections.get_connection()
self.index = self.document._doc_type.index
self.mapping = self.document._doc_type.mapping.properties.name
self.search = Search(
using=self.client,
index=self.index,
doc_type=self.document._doc_type.name
)
super(BaseDocumentViewSet, self).__init__(*args, **kwargs)
[docs] def get_queryset(self):
"""Get queryset."""
return self.search.query()
[docs] def get_object(self):
"""Get object."""
queryset = self.get_queryset()
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
if lookup_url_kwarg not in self.kwargs:
raise AttributeError(
"Expected view %s to be called with a URL keyword argument "
"named '%s'. Fix your URL conf, or set the `.lookup_field` "
"attribute on the view correctly." % (
self.__class__.__name__,
lookup_url_kwarg
)
)
if lookup_url_kwarg == 'id':
obj = self.document.get(id=self.kwargs[lookup_url_kwarg])
return DictionaryProxy(obj.to_dict())
else:
queryset = queryset.filter(
'term',
**{self.document_uid_field: self.kwargs[lookup_url_kwarg]}
)
count = queryset.count()
if count == 1:
obj = queryset.execute().hits.hits[0]['_source']
return DictionaryProxy(obj)
elif count > 1:
raise Http404(
"Multiple results matches the given query. "
"Expected a single result."
)
raise Http404("No result matches the given query.")
[docs]class DocumentViewSet(BaseDocumentViewSet,
SuggestMixin,
FunctionalSuggestMixin):
"""DocumentViewSet with suggest and functional-suggest mix-ins."""