Proposing results from a remote API

This documentation is optionnal, but it is complementary with all other documentation. It aims advanced users.

Consider a social network about music. In order to propose all songs in the world in its autocomplete, it should either:

  • have a database with all songs of the world,
  • use a simple REST API to query a database with all songs world

The purpose of this documentation is to describe every elements involved. Note that a living demonstration is available in test_remote_project, where one project serves a full database of cities via an API to another.

Example

In test_remote_project/remote_autocomplete, of course you should not hardcode urls like that in actual projects:

from cities_light.contrib.autocompletes import *

import autocomplete_light


autocomplete_light.register(Country, CountryRestAutocomplete,
    source_url='http://localhost:8000/cities_light/country/')

autocomplete_light.register(Region, RegionRestAutocomplete,
    source_url='http://localhost:8000/cities_light/region/')

autocomplete_light.register(City, CityRestAutocomplete,
    source_url='http://localhost:8000/cities_light/city/')

Check out the documentation of RemoteCountryChannel and RemoteCityChannel for more.

API

class autocomplete_light.autocomplete.rest_model.AutocompleteRestModel[source]
download(url)[source]

Given an url to a remote object, return the corresponding model from the local database.

The default implementation expects url to respond with a JSON dict of the attributes of an object.

For relation attributes, it expect the value to be another url that will respond with a JSON dict of the attributes of the related object.

It calls model_for_source_url() to find which model class corresponds to which url. This allows download() to be recursive.

download_choice(choice)[source]

Take a choice’s dict representation, return it’s local pk which might have been just created.

If your channel works with 0 to 1 API call, consider overriding this method. If your channel is susceptible of using several different API calls, consider overriding download().

get_remote_choices(max)[source]

Parses JSON from the API, return model instances.

The JSON should contain a list of dicts. Each dict should contain the attributes of an object. Relation attributes should be represented by their url in the API, which is set to model._source_url.

get_source_url(limit)[source]

Return an API url for the current autocomplete request.

By default, return self.source_url with the data dict returned by get_source_url_data().

get_source_url_data(limit)[source]

Given a limit of items, return a dict of data to send to the API.

By default, it passes current request GET arguments, along with format: ‘json’ and the limit.

model_for_source_url(url)[source]

Take an URL from the API this remote channel is supposed to work with, return the model class to use for that url.

It is only needed for the default implementation of download(), because it has to follow relations recursively.

By default, it will return the model of self.choices.

Javascript fun

Channels with bootstrap=’remote’ get a deck using RemoteChannelDeck’s getValue() rather than the default getValue() function.

if (window.yourlabs === undefined) window.yourlabs = {};

yourlabs.RemoteAutocompleteWidget = {
    /*
    The default deck getValue() implementation just returns the PK from the
    choice HTML. RemoteAutocompleteWidget.getValue's implementation checks for
    a url too. If a url is found, it will post to that url and expect the pk to
    be in the response.

    This is how autocomplete-light supports proposing values that are not there
    in the database until user selection.
    */
    getValue: function(choice) {
        var value = choice.data('value');

        if (typeof(value) === 'string' && isNaN(value) && value.match(/^https?:/)) {
            $.ajax(this.autocompleteOptions.url, {
                async: false,
                type: 'post',
                data: {
                    'value': value
                },
                success: function(text, jqXHR, textStatus) {
                    value = text;
                }
            });

            choice.data('value', value);
        }

        return value;
    }
}

$(document).bind('yourlabsWidgetReady', function() {
    // Instanciate decks with RemoteAutocompleteWidget as override for all widgets with
    // autocomplete 'remote'.
    $('body').on('initialize', '.autocomplete-light-widget[data-bootstrap=rest_model]', function() {
        $(this).yourlabsWidget(yourlabs.RemoteAutocompleteWidget);
    });
});