Custom Search Views

Note

Note: Most portals shouldn’t need these changes, it’s only necessary if you need finer control over the low level components that make requests to the Globus Search service. Try customizing your search templates [here first](https://github.com/globusonline/django-globus-portal-framework/wiki/Customizing-Fields-and-Templates).

At some point, you may need more control over the context for your search templates. An example may be rendering graphs based on the statistics returned by facets. The examples below include writing your own ‘search’ view, and wiring it up to override the default search view provided by Globus Portal Framework. This requires two changes:

  • views.py – Writing a custom view to capture a user search parameters and return context to the portal

  • urls.py – Apply the custom view to one or more of your indices

In your project’s urls.py:

from django.urls import path, include
from globus_portal_framework.urls import register_custom_index
from exalearn.views import mysearch

# Register a new custom index converter.
register_custom_index('custom_search', ['myindex'])

urlpatterns = [
    # Override 'search' url with custom search view
    path('<custom_search:index>/', mysearch, name='search'),
    # Provides the basic search portal
    path('', include('globus_portal_framework.urls')),
    path('', include('globus_portal_framework.urls_debugging')),
    path('', include('social_django.urls', namespace='social')),
]

The URLs are still very similar to the old ones, except for the register_custom_index line. This registers a new [Django URL Converter](https://docs.djangoproject.com/en/2.2/topics/http/urls/#registering-custom-path-converters) for the indices you include in the second argument. This does a couple things:

  • Only the indices you include will use the new functionality

  • Unrelated URLs won’t match as an ‘index’, such as if a bot searches for ‘/robots.txt’. Only URLs which map to the indices you include in the list will be matched.

In your project’s views.py:

from django.shortcuts import render
from globus_portal_framework.gsearch import post_search, get_search_query, get_search_filters, get_template

def mysearch(request, index):
    query = get_search_query(request)
    filters = get_search_filters(request)
    context = {'search': post_search(index, query, filters, request.user,
                                     request.GET.get('page', 1))}
    return render(request, get_template(index, 'search.html'), context)

With the above, we’re using a number of components to prepare and process the search:

  • get_search_query fetches the user’s query from the query params on the request

  • get_search_filters fetches any filters in query params that should be applied to the search

  • post_search prepares and sends the request to Globus Search, in addition to processing the results based on configuration defined in settings.SEARCH_INDEXES

  • get_template will attempt to grab an overridden custom index template if it exists (templates/myindex/search.html), grab a standard overridden template (templates/search.html), or simply render the basic Globus Portal Framework search.html template

We Need To Go Deeper

It’s possible to access the lowest level mechanism to modify requests made to Globus Search. Again, most use-cases shouldn’t require this, but it may be necessary if you need to utilize Globus Search feature not provided by Globus Portal Framework. If this is the case, consider opening an issue, so we can provide the feature for others.

Advanced search views.py

from django.shortcuts import render
from globus_portal_framework.gsearch import (
    get_search_query, get_search_filters,
    process_search_data, get_facets, get_template, get_index
)
from globus_portal_framework.gclients import load_search_client


def my_advanced_search(request, index):
    index_data = get_index(index)
    search_cli = load_search_client(request.user)
    query = get_search_query(request)
    filters = get_search_filters(request)
    data = {'q': query,
            'filters': filters}
    result = search_cli.post_search(index_data['uuid'], data)
    search_data = {
        'search_results': process_search_data(index_data.get('fields', []),
                                              result.data['gmeta']),
        'facets': get_facets(result, index_data.get('facets', []),
                             filters, index_data.get('filter_match')),
    }
    context = {'search': search_data}
    return render(request, get_template(index, 'search.html'), context)

The custom search function above allows for extended flexibility in what gets sent to Globus Search and the resulting context you want rendered in your templates. There are a few new components we’re using:

  • get_index Will search settings.SEARCH_INDEXES for your index, and return data associated with it.

  • load_search_client Will fetch the base globus_sdk.SearchClient class loaded with an authorizer for the current user (Or nothing, if the user is logged out).

  • process_search_data applies the fields defined in settings.SEARCH_INDEXES to the search data returned by Globus Search.

  • get_facets processes the facet data returned by Globus Search, and prepares the context so that users can filter on those facets on their next action.