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_api_project, where one project serves a full database of cities via an API to another.

Example

In test_api_project, of course you should not hardcode urls like that in actual projects:

import autocomplete_light

from cities_light.contrib.autocomplete_light_restframework import RemoteCountryChannel, RemoteCityChannel
from cities_light.models import City, Country

class RemoteCountryChannel(RemoteCountryChannel):
    source_url = 'http://localhost:8000/cities_light/country/'

class RemoteCityChannel(RemoteCityChannel):
    source_url = 'http://localhost:8000/cities_light/city/'

autocomplete_light.register(Country, RemoteCountryChannel)
autocomplete_light.register(City, RemoteCityChannel)

Check out the documentation of RemoteCountryChannel and RemoteCityChannel for more.

API

class autocomplete_light.channel.remote.RemoteChannelBase[source]

Uses an API to propose suggestions from an HTTP API, tested with djangorestframework.

model_for_source_url
A very important function to override! take an API URL and return the corresponding model class. This is API specific, there is a complete example in cities_light.contrib.
source_url
The full URL to the list API. For example, to a djangorestframework list view.

An example implementation usage is demonstrated in the django-cities-light contrib folder.

Autocomplete box display chronology:

  • autocomplete.js requests autocomplete box to display for an input,
  • get_results() fetches some extra results via get_remote_results(),
  • get_remote_results() calls source_url and returns a list of models,
  • the remote results are rendered after the local results in widget.html. It includes some JSON in a hidden textarea, like the API’s url for each result.

Remote result selection chronology:

  • deck.js calls remoteGetValue() instead of the default getValue(),
  • remoteGetValue() posts the json from the result to ChannelView,
  • ChannelView.post() does its job of proxying RemoteChannelBase.post(),
  • RemoteChannelBase.post() returns an http response which body is just the pk of the result in the local database, using self.fetch_result(),
  • self.fetch_result() passes the API url of the result and recursively saves the remote models into the local database, returning the id of the newly created object.

Set result_template and autocomplete_template if necessary.

fetch(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 fetch() to be recursive.

fetch_result(result)[source]

Take a result’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 fetch().

get_remote_results(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_results(values=None)[source]

Returns a list of results from both the local database and the API if in the context of a request.

Using self.limit_results and the number of local results, adds results from get_remote_results().

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 fetch(), because it has to follow relations recursively.

post(request, *args, **kwargs)[source]

Take POST variable ‘result’, install it in the local database, return the newly created id.

The HTTP response has status code 201 Created.

result_as_dict(result)[source]

Return the result pk or _source_url.

result_as_value(result)[source]

Return the result pk or source url.

Javascript fun

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

var RemoteChannelDeck = {
    // The default deck getValue() implementation just returns the PK from the
    // result HTML. RemoteChannelDeck's implementation checks for a textarea
    // that would contain a JSON dict in the result's HTML. If the dict has a
    // 'value' key, then return this value. Otherwise, make a blocking ajax
    // request: POST the json dict to the channel url. It expects that the
    // response will contain the value.
    getValue: function(result) {
        data = $.parseJSON(result.find('textarea').html());

        if (data.value) return data.value;

        var value = false;
        $.ajax(this.payload.channel.url, {
            async: false,
            type: 'post',
            data: {
                'result': result.find('textarea').html(),
            },
            success: function(text, jqXHR, textStatus) {
                value = text;
            }
        });

        return value;
    }
}

// Instanciate decks with RemoteChannelDeck as override for all widgets with
// channel 'remote'.
$('.autocomplete_light_widget[data-bootstrap=remote]').each(function() {
    $(this).yourlabs_deck(RemoteChannelDeck);
});

Project Versions

Table Of Contents

Previous topic

GenericForeignKey support

Next topic

Django 1.3 support workarounds

This Page