Skip to content

Fix for version 8 of ES #321

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 78 additions & 115 deletions src/django_elasticsearch_dsl_drf/filter_backends/suggester/functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,30 +67,31 @@
>>>
>>> model = Publisher # The model associate with this Document
"""

from elasticsearch_dsl.search import AggsProxy

from django_elasticsearch_dsl_drf.constants import (
FUNCTIONAL_SUGGESTER_TERM_MATCH,
FUNCTIONAL_SUGGESTER_PHRASE_MATCH,
FUNCTIONAL_SUGGESTER_COMPLETION_PREFIX,
FUNCTIONAL_SUGGESTER_COMPLETION_MATCH,
ALL_FUNCTIONAL_SUGGESTERS,
)
from django_elasticsearch_dsl_drf.utils import EmptySearch
try:
from elasticsearch_dsl.search_base import AggsProxy
except ImportError:
from elasticsearch_dsl.search import AggsProxy

from rest_framework.exceptions import ValidationError
from rest_framework.filters import BaseFilterBackend

from six import string_types

from django_elasticsearch_dsl_drf.constants import (
ALL_FUNCTIONAL_SUGGESTERS,
FUNCTIONAL_SUGGESTER_COMPLETION_MATCH,
FUNCTIONAL_SUGGESTER_COMPLETION_PREFIX,
)

from ..mixins import FilterBackendMixin

__title__ = 'django_elasticsearch_dsl_drf.filter_backends.suggester.' \
'functional'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2017-2020 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = ('FunctionalSuggesterFilterBackend',)
__title__ = "django_elasticsearch_dsl_drf.filter_backends.suggester." "functional"
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
__copyright__ = "2017-2020 Artur Barseghyan"
__license__ = "GPL 2.0/LGPL 2.1"
__all__ = ("FunctionalSuggesterFilterBackend",)


class FunctionalSuggesterFilterBackend(BaseFilterBackend, FilterBackendMixin):
Expand Down Expand Up @@ -172,16 +173,12 @@ def prepare_suggester_fields(cls, view):

for field, options in filter_fields.items():
if options is None or isinstance(options, string_types):
filter_fields[field] = {
'field': options or field
}
elif 'field' not in filter_fields[field]:
filter_fields[field]['field'] = field
filter_fields[field] = {"field": options or field}
elif "field" not in filter_fields[field]:
filter_fields[field]["field"] = field

if 'suggesters' not in filter_fields[field]:
filter_fields[field]['suggesters'] = tuple(
ALL_FUNCTIONAL_SUGGESTERS
)
if "suggesters" not in filter_fields[field]:
filter_fields[field]["suggesters"] = tuple(ALL_FUNCTIONAL_SUGGESTERS)

return filter_fields

Expand Down Expand Up @@ -239,19 +236,16 @@ def apply_query_size(cls, queryset, options):
:param options:
:return:
"""
if 'size' in options['options']:
if "size" in options["options"]:
queryset = queryset.extra(
from_=options['options'].get('from', 0),
size=options['options']['size']
from_=options["options"].get("from", 0), size=options["options"]["size"]
)
return queryset

@classmethod
def apply_suggester_completion_prefix(cls,
suggester_name,
queryset,
options,
value):
def apply_suggester_completion_prefix(
cls, suggester_name, queryset, options, value
):
"""Apply `completion` suggester prefix.

This is effective when used with Keyword fields.
Expand All @@ -267,19 +261,12 @@ def apply_suggester_completion_prefix(cls,
:return: Modified queryset.
:rtype: elasticsearch_dsl.search.Search
"""
queryset = queryset.query(
'prefix',
**{options['field']: value}
)
queryset = queryset.query("prefix", **{options["field"]: value})
queryset = cls.apply_query_size(queryset, options)
return queryset

@classmethod
def apply_suggester_completion_match(cls,
suggester_name,
queryset,
options,
value):
def apply_suggester_completion_match(cls, suggester_name, queryset, options, value):
"""Apply `completion` suggester match.

This is effective when used with Ngram fields.
Expand All @@ -295,10 +282,7 @@ def apply_suggester_completion_match(cls,
:return: Modified queryset.
:rtype: elasticsearch_dsl.search.Search
"""
queryset = queryset.query(
'match',
**{options['field']: value}
)
queryset = queryset.query("match", **{options["field"]: value})
queryset = cls.apply_query_size(queryset, options)
return queryset

Expand All @@ -317,72 +301,64 @@ def get_suggester_query_params(self, request, view):
suggester_query_params = {}
suggester_fields = self.prepare_suggester_fields(view)
for query_param in query_params:
query_param_list = self.split_lookup_filter(
query_param,
maxsplit=1
)
query_param_list = self.split_lookup_filter(query_param, maxsplit=1)
field_name = query_param_list[0]

if field_name in suggester_fields:
suggester_param = None
if len(query_param_list) > 1:
suggester_param = query_param_list[1]

valid_suggesters = suggester_fields[field_name]['suggesters']
valid_suggesters = suggester_fields[field_name]["suggesters"]

suggester_options = {}

# If we have default suggester given use it as a default and
# do not require further suffix specification.
default_suggester = None
if 'default_suggester' in suggester_fields[field_name]:
default_suggester = \
suggester_fields[field_name]['default_suggester']
if "default_suggester" in suggester_fields[field_name]:
default_suggester = suggester_fields[field_name][
"default_suggester"
]

if 'options' in suggester_fields[field_name]:
suggester_options = suggester_fields[field_name]['options']
if "options" in suggester_fields[field_name]:
suggester_options = suggester_fields[field_name]["options"]

if suggester_param is None \
or suggester_param in valid_suggesters:
if suggester_param is None or suggester_param in valid_suggesters:

# If we have default suggester given use it as a default
# and do not require further suffix specification.
if suggester_param is None \
and default_suggester is not None:
if suggester_param is None and default_suggester is not None:
suggester_param = str(default_suggester)

values = [
__value.strip()
for __value
in query_params.getlist(query_param)
if __value.strip() != ''
for __value in query_params.getlist(query_param)
if __value.strip() != ""
]

# If specific field given, use that. Otherwise,
# fall back to the top level field name.
if 'serializer_field' in suggester_fields[field_name]:
serializer_field = \
suggester_fields[field_name]['serializer_field']
if "serializer_field" in suggester_fields[field_name]:
serializer_field = suggester_fields[field_name][
"serializer_field"
]
else:
serializer_field = suggester_fields[field_name].get(
'field',
field_name
)
serializer_field = self.extract_field_name(
serializer_field
"field", field_name
)
serializer_field = self.extract_field_name(serializer_field)

if values:
suggester_query_params[query_param] = {
'suggester': suggester_param,
'values': values,
'field': suggester_fields[field_name].get(
'field',
field_name
"suggester": suggester_param,
"values": values,
"field": suggester_fields[field_name].get(
"field", field_name
),
'type': view.mapping,
'serializer_field': serializer_field,
'options': suggester_options,
"type": view.mapping,
"serializer_field": serializer_field,
"options": suggester_options,
}
return suggester_query_params

Expand All @@ -396,17 +372,13 @@ def clean_queryset(self, queryset):
:param queryset:
:return:
"""
queryset.aggs = AggsProxy('')
queryset.aggs = AggsProxy("")
queryset._highlight = {}
queryset._sort = ['_score']
queryset._sort = ["_score"]
queryset._functional_suggest = True
return queryset

def serialize_queryset(self,
queryset,
suggester_name,
value,
serializer_field):
def serialize_queryset(self, queryset, suggester_name, value, serializer_field):
"""Serialize queryset.

This shall be done here, since we don't want to delegate it to
Expand All @@ -420,18 +392,20 @@ def serialize_queryset(self,
"""
result = queryset.execute().to_dict()
hits = []
for hit in result['hits']['hits']:
hit.update({'text': hit['_source'].get(serializer_field)})
for hit in result["hits"]["hits"]:
hit.update({"text": hit["_source"].get(serializer_field)})
hits.append(hit)

data = {
suggester_name: [{
'text': value,
'options': hits,
'length': len(value),
'offset': 0,
}],
'_shards': result['_shards'],
suggester_name: [
{
"text": value,
"options": hits,
"length": len(value),
"offset": 0,
}
],
"_shards": result["_shards"],
}
return data

Expand All @@ -445,7 +419,7 @@ def extract_field_name(self, field_name):
:return:
:rtype: str
"""
return field_name.split('.')[0]
return field_name.split(".")[0]

def filter_queryset(self, request, queryset, view):
"""Filter the queryset.
Expand All @@ -462,7 +436,7 @@ def filter_queryset(self, request, queryset, view):
# The ``SuggesterFilterBackend`` filter backend shall be used in
# the ``suggest`` custom view action/route only. Usages outside of the
# are ``suggest`` action/route are restricted.
if view.action != 'functional_suggest':
if view.action != "functional_suggest":
return queryset

# Clean the queryset.
Expand All @@ -477,33 +451,25 @@ def filter_queryset(self, request, queryset, view):

for suggester_name, options in suggester_query_params.items():
# We don't have multiple values here.
for value in options['values']:
for value in options["values"]:
# `completion` suggester
if options['suggester'] == \
FUNCTIONAL_SUGGESTER_COMPLETION_PREFIX:
if options["suggester"] == FUNCTIONAL_SUGGESTER_COMPLETION_PREFIX:
queryset = self.apply_suggester_completion_prefix(
suggester_name,
queryset,
options,
value
suggester_name, queryset, options, value
)
applied = True
picked_suggester_name = suggester_name
picked_value = value
picked_serializer_field = options['serializer_field']
picked_serializer_field = options["serializer_field"]

elif options['suggester'] == \
FUNCTIONAL_SUGGESTER_COMPLETION_MATCH:
elif options["suggester"] == FUNCTIONAL_SUGGESTER_COMPLETION_MATCH:
queryset = self.apply_suggester_completion_match(
suggester_name,
queryset,
options,
value
suggester_name, queryset, options, value
)
applied = True
picked_suggester_name = suggester_name
picked_value = value
picked_serializer_field = options['serializer_field']
picked_serializer_field = options["serializer_field"]

# # `term` suggester
# elif options['suggester'] == SUGGESTER_TERM:
Expand All @@ -529,8 +495,5 @@ def filter_queryset(self, request, queryset, view):
# return empty_queryset

return self.serialize_queryset(
queryset,
picked_suggester_name,
picked_value,
picked_serializer_field
queryset, picked_suggester_name, picked_value, picked_serializer_field
)
Loading