From 3aecb651e0ca3bc4b40ab48959c88559df87ea5b Mon Sep 17 00:00:00 2001 From: Akiff Manji Date: Tue, 6 Jun 2023 17:12:01 -0700 Subject: [PATCH] feat: temporarily re-introduce v4/search/autocomplete Signed-off-by: Akiff Manji --- .../api/v4/search/filters/autocomplete.py | 14 +++ .../api/v4/serializers/search/autocomplete.py | 28 +++++ .../api/v4/serializers/search/topic.py | 22 ++-- server/vcr-server/api/v4/urls.py | 16 ++- .../api/v4/views/search/autocomplete.py | 105 ++++++++++++++++++ 5 files changed, 167 insertions(+), 18 deletions(-) create mode 100644 server/vcr-server/api/v4/search/filters/autocomplete.py create mode 100644 server/vcr-server/api/v4/serializers/search/autocomplete.py create mode 100644 server/vcr-server/api/v4/views/search/autocomplete.py diff --git a/server/vcr-server/api/v4/search/filters/autocomplete.py b/server/vcr-server/api/v4/search/filters/autocomplete.py new file mode 100644 index 000000000..7e18c5f71 --- /dev/null +++ b/server/vcr-server/api/v4/search/filters/autocomplete.py @@ -0,0 +1,14 @@ +# DEPRECATED: this should not be used in new code and will be removed imminently + +from api.v3.search_filters import AutocompleteFilter as V3AutocompleteFilter +from api.v3.search_filters import get_autocomplete_builder + + +class AutocompleteFilter(V3AutocompleteFilter): + """ + Apply name autocomplete filter to credential search + """ + + query_builder_class = get_autocomplete_builder( + ("name_text", "address_civic_address", "topic_source_id", "topic_name") + ) diff --git a/server/vcr-server/api/v4/serializers/search/autocomplete.py b/server/vcr-server/api/v4/serializers/search/autocomplete.py new file mode 100644 index 000000000..baca25a27 --- /dev/null +++ b/server/vcr-server/api/v4/serializers/search/autocomplete.py @@ -0,0 +1,28 @@ +# DEPRECATED: this should not be used in new code and will be removed imminently + +from rest_framework.serializers import SerializerMethodField + +from api.v3.indexes.Topic import TopicIndex + +from api.v2.serializers.rest import TopicSerializer + +from api.v3.serializers.search import ( + TopicAutocompleteSerializer as V3TopicAutocompleteSerializer, +) + + +class TopicAutocompleteSerializer(V3TopicAutocompleteSerializer): + names = SerializerMethodField() + + @staticmethod + def get_names(obj): + return [ + name.text + for name in obj.object.get_active_names() + if name.type == "entity_name" + ] + + class Meta(TopicSerializer.Meta): + index_classes = [TopicIndex] + fields = ("id", "type", "sub_type", "value", "score", "topic_source_id", + "topic_type", "credential_id", "credential_type", "names",) diff --git a/server/vcr-server/api/v4/serializers/search/topic.py b/server/vcr-server/api/v4/serializers/search/topic.py index 144ce4d12..e3cb4d226 100644 --- a/server/vcr-server/api/v4/serializers/search/topic.py +++ b/server/vcr-server/api/v4/serializers/search/topic.py @@ -1,23 +1,16 @@ -import logging from collections import OrderedDict from rest_framework.serializers import BooleanField, CharField, DateTimeField, IntegerField, SerializerMethodField -from drf_haystack.serializers import HaystackSerializer, HaystackFacetSerializer +from drf_haystack.serializers import HaystackSerializer -from api.v2.models.Address import Address -from api.v2.models.Credential import Credential from api.v2.models.CredentialType import CredentialType from api.v2.models.Issuer import Issuer -from api.v2.models.Name import Name -from api.v2.models.Topic import Topic from api.v2.serializers.rest import ( AddressSerializer, - CredentialSerializer, CredentialSetSerializer, CredentialTypeSerializer, NameSerializer, - TopicSerializer, TopicAttributeSerializer, ) from api.v2.serializers.search import ( @@ -28,10 +21,10 @@ facet_filter_display_map = { - 'topic_category': 'category', - 'topic_issuer_id': 'issuer_id', - 'topic_type_id': 'type_id', - 'topic_credential_type_id': 'credential_type_id', + "topic_category": "category", + "topic_issuer_id": "issuer_id", + "topic_type_id": "type_id", + "topic_credential_type_id": "credential_type_id", } @@ -88,7 +81,8 @@ class Meta: fields = ("id", "source_id", "type", "names", "addresses", "attributes", "credential_set", "credential_type", "inactive", "revoked", "effective_date", "revoked_date") # ExactFilter fields - exact_fields = ("topic_issuer_id", "topic_type_id", "topic_credential_type_id") + exact_fields = ("topic_issuer_id", "topic_type_id", + "topic_credential_type_id") # StatusFilter fields status_fields = {"topic_inactive": "false", "topic_revoked": "false"} # HaystackFilter fields @@ -125,7 +119,7 @@ def format_facet_text(self, field_name, facets): if len(rows): text = { field_name: { - str(row['id']): row[field_selector] for row in rows.values() + str(row["id"]): row[field_selector] for row in rows.values() } } diff --git a/server/vcr-server/api/v4/urls.py b/server/vcr-server/api/v4/urls.py index f52f7f612..2f5108b36 100644 --- a/server/vcr-server/api/v4/urls.py +++ b/server/vcr-server/api/v4/urls.py @@ -9,7 +9,9 @@ from api.v4.views.search import ( topic as search_topic, credential as search_credential, - fuzzy as search_fuzzy + fuzzy as search_fuzzy, + # DEPRECATED: this should not be used in new code and will be removed imminently + autocomplete as search_autocomplete, ) from api.v4.views.rest import credential_type, issuer, topic, schemas from api.v4.views.misc.contact import send_contact @@ -34,13 +36,18 @@ router = SimpleRouter(trailing_slash=False) -router.register(r"credential-type", credential_type.RestView, "Credential Type") +router.register(r"credential-type", + credential_type.RestView, "Credential Type") router.register(r"issuer", issuer.RestView, "Issuer") router.register(r"schemas", schemas.RestView) router.register(r"topic", topic.RestView) -router.register(r"search/credential", search_credential.SearchView, "Credential Search") +router.register(r"search/credential", + search_credential.SearchView, "Credential Search") router.register(r"search/topic", search_topic.SearchView, "Topic Search") router.register(r"search/fuzzy", search_fuzzy.SearchView, "Fuzzy Search") +# DEPRECATED: this should not be used in new code and will be removed imminently +router.register(r"search/autocomplete", + search_autocomplete.SearchView, "Aggregate Autocomplete") # Misc endpoints miscPatterns = [ @@ -52,4 +59,5 @@ path("", schema_view.with_ui("swagger", cache_timeout=None), name="api-docs") ] -urlpatterns = format_suffix_patterns(router.urls) + miscPatterns + swaggerPatterns +urlpatterns = format_suffix_patterns( + router.urls) + miscPatterns + swaggerPatterns diff --git a/server/vcr-server/api/v4/views/search/autocomplete.py b/server/vcr-server/api/v4/views/search/autocomplete.py new file mode 100644 index 000000000..72711c915 --- /dev/null +++ b/server/vcr-server/api/v4/views/search/autocomplete.py @@ -0,0 +1,105 @@ +# DEPRECATED: this should not be used in new code and will be removed imminently + +import logging + +from drf_yasg import openapi +from drf_yasg.utils import swagger_auto_schema +from drf_haystack.serializers import HaystackSerializer + +from api.v3.views.search import ( + AggregateAutocompleteView, + aggregate_autocomplete_swagger_params as swagger_params, +) +from api.v3.serializers.search import ( + AddressAutocompleteSerializer, + NameAutocompleteSerializer, + # TODO: create a v4 version of this serializer + TopicAutocompleteSerializer, +) +from api.v3.search_filters import StatusFilter as AutocompleteStatusFilter +from api.v4.search.filters.autocomplete import AutocompleteFilter +from api.v4.search.filters.topic import ( + TopicCategoryFilter as AutocompleteCategoryFilter, + TopicExactFilter as AutocompleteExactFilter, +) +from api.v3.indexes import ( + Address as AddressIndex, + Name as NameIndex, + Topic as TopicIndex, +) + +LOGGER = logging.getLogger(__name__) + +_swagger_params = [ + openapi.Parameter( + "category", + openapi.IN_QUERY, + description="Filter by Credential Category. The category name and value should be joined by '::'", + type=openapi.TYPE_STRING, + ), + openapi.Parameter( + "type_id", + openapi.IN_QUERY, + description="Filter by Credential Type ID of the Topic", + type=openapi.TYPE_STRING, + ), + openapi.Parameter( + "issuer_id", + openapi.IN_QUERY, + description="Filter by Issuer ID of the Topic", + type=openapi.TYPE_STRING, + ), + openapi.Parameter( + "credential_type_id", + openapi.IN_QUERY, + description="Filter by Credential Type ID of any credentials owned by the Topic", + type=openapi.TYPE_STRING, + ), + # Put additional parameters here +] + list(swagger_params) + + +class AggregateAutocompleteSerializer(HaystackSerializer): + class Meta: + serializers = { + AddressIndex: AddressAutocompleteSerializer, + NameIndex: NameAutocompleteSerializer, + TopicIndex: TopicAutocompleteSerializer, + } + + filter_fields_map = { + "category": ("topic_category",), + "issuer_id": ("topic_issuer_id"), + "type_id": ("topic_type_id"), + "credential_type_id": ("topic_credential_type_id"), + "inactive": ( + "address_credential_inactive", + "name_credential_inactive", + "topic_all_credentials_inactive", + ), + "revoked": ( + "address_credential_revoked", + "name_credential_revoked", + "topic_all_credentials_revoked", + ), + } + + +class SearchView(AggregateAutocompleteView): + """ + Return autocomplete results for a query string + """ + + @swagger_auto_schema( + manual_parameters=_swagger_params, + responses={200: AggregateAutocompleteSerializer(many=True)}, + ) + def list(self, *args, **kwargs): + return super(SearchView, self).list(*args, **kwargs) + + filter_backends = ( + AutocompleteFilter, + AutocompleteStatusFilter, + AutocompleteCategoryFilter, + AutocompleteExactFilter, + )