Source code for autocomplete_light.autocomplete.rest_model

import urllib
import six

from django import http
from django.utils.encoding import force_text

from .model import AutocompleteModel

try:
    import json
except ImportError:
    from django.utils import simplejson as json


[docs]class AutocompleteRestModel(AutocompleteModel): widget_js_attributes = {'bootstrap': 'rest_model'} @property def model(self): return self.choices.model def post(self, request, *args, **kwargs): value = request.POST['value'] pk = self.download_choice(value) return http.HttpResponse(pk, status=201)
[docs] def get_source_url(self, limit): """ 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(). """ return '%s?%s' % (self.source_url, urllib.urlencode( self.get_source_url_data(limit)))
[docs] def get_source_url_data(self, limit): """ 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. """ data = {} if self.request: for key, value in self.request.GET.items(): data[key] = value data.update({ 'format': 'json', 'limit': limit, }) return data
[docs] def model_for_source_url(self, url): """ 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. """ return self.choices.model
def choices_for_request(self): choices = super(AutocompleteRestModel, self).choices_for_request() unicodes = [force_text(choice) for choice in choices] slots = self.limit_choices - len(choices) if slots > 0: choices = list(choices) for choice in self.get_remote_choices(slots): # avoid data that's already in local if force_text(choice) in unicodes: continue choices.append(choice) return choices
[docs] def get_remote_choices(self, max): """ 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. """ url = self.get_source_url(max) try: fh = urllib.urlopen(url) body = fh.read() except: return else: for data in json.loads(body): url = data.pop('url') for name in data.keys(): field = self.model._meta.get_field_by_name(name)[0] if getattr(field, 'rel', None): data.pop(name) model = self.model(**data) model.pk = url yield model
[docs] def download_choice(self, choice): """ 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(). """ return self.download(choice).pk
[docs] def download(self, url): """ 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. """ model_class = self.model_for_source_url(url) fh = urllib.urlopen(url) data = json.loads(fh.read()) data.pop('url') fh.close() uniques = [f.name for f in model_class._meta.fields if f.unique] unique_data = {} for key, value in data.items(): if key not in uniques: continue field = model_class._meta.get_field_by_name(key)[0] if getattr(field, 'rel', False): continue unique_data[key] = value if not unique_data: assert self.get_or_create_by, 'get_or_create_by needed' for key in self.get_or_create_by: if key in data.keys() and data[key]: unique_data[key] = data[key] if not len(unique_data.keys()): raise Exception('cannot check if this model exists locally') try: model = model_class.objects.get(**unique_data) except model_class.DoesNotExist: model = model_class(**unique_data) for key, value in data.items(): is_string = isinstance(value, six.string_types) field = model_class._meta.get_field_by_name(key)[0] if getattr(field, 'rel', None) and is_string: setattr(model, key, self.download(value)) else: setattr(model, key, value) model.save() return model