california app design company

Integrating Google Places API and Django TastyPie

November 13, 2012

The Object

TastyPie is a REST API library built for the Django framework. It primarily serves as an out of the box solution for serving up data for your Django ORM models. The library also has a non-ORM function, however it still revolves around creating, reading, updating, or deleting objects. For our latest project, BlurtAbout, we had to integrate with a location based API so we chose Google Places.

class VenueObject(object):
    def __init__(self, id=None, name=None, address=None):
        self.id = id
        self.name = name
        self.address = address

Above is the basic object definition for our "Place" object, which is sent down to the application from our API. The Google Places API also provides other information, but this is all we needed for the task at hand. Essentially we will be setting up our TastyPie resource to send down either one or many of these objects representing places from Google's API.

The Resource

class VenuesResource(Resource):
    id = fields.CharField(attribute='id')
    name = fields.CharField(attribute='name')
    address = fields.CharField(attribute='address', null=True)

    class Meta:
        resource_name = 'venues'
        allowed_methods = ['get', 'post']
        object_class = VenueObject
        authorization = Authorization()
        authentication = BasicAuthentication()

The fields in this resource define what data we're looking for from POSTS and what data we may pass back for consumption. The resource_name defines our API's url (ex. /api/v1/venues/). The allowed_methods show that we're only allowing the creation of new venues via POSTs or grabbing read only venues via GETs. Our object_class dictates what python objects we will be serializing into the API response. The authorization/authentication values are standard TastyPie methods.

Library of Choice

def _client(self):
    return GooglePlaces(settings.GOOGLE_API_KEY)

Within our resource we have the Google Place's client we will be using defined. This is using a very convient Google Places python library from github. We've also stored our Google API Key in our settings file, which is used here.

Primary Keys

def detail_uri_kwargs(self, bundle_or_obj):
    kwargs = {}

    if isinstance(bundle_or_obj, Bundle):
        kwargs['pk'] = bundle_or_obj.obj.id
    else:
        kwargs['pk'] = bundle_or_obj['id']

    return kwargs

Since TastyPie only deals with objects and is REST based it is important to have primary keys for all locations we will be dealing with. The above snippet sets the primary key of the current TastyPie object by either using the ID of the VenueObject or from the data bundle passed through the API.

GET

def obj_get(self, request=None, **kwargs):
    result = self._client().get_place(kwargs['pk'])
    result.get_details()
    return VenueObject(result.reference, result.name, result.formatted_address)

When retrieving a specific Google Place we will have passed in the ID of this object through the API (ex. /api/v1/venues/10/). We use our library to get an instance of it and call the get_place() function with that primary key. Google responds to us with a Place object that has a very limited amount of information. We call get_details() on this object so that we can populate our object with more information. We then return a VenueObject passing in the reference, name, and address of the Google Place object. We use reference instead of ID as recommended by the Google Places API. TastyPie then does its magic to take the VenueObject and pass down a JSON formatted object to the requestor.

POST

def obj_create(self, bundle, request=None, **kwargs):
    result = self._client().add_place(name=str(bundle.data['name']),
        lat_lng={'lat':bundle.data['latitude'],'lng':bundle.data['longitude']},
        accuracy=bundle.data['accuracy'],
        types=str(bundle.data['type']),
        language=lang.ENGLISH,
        sensor=True)
    bundle.obj = VenueObject(result['reference'], bundle.data['name'])
    bundle = self.full_hydrate(bundle)
    return bundle

Google Places allows the creation of new locations via their API. Initially these locations become available to just your application and at Google's discretion they may add some of your locations to their entire database. This is still valuable though, because users can generate content that you can reuse again within your application without the possibility of that data being deleted or rejected on Google's end.

In order to create a new location via the API there are several inputs we need. The obvious data points here are the name of the location, lat/long, and language. The less obvious variables are accuracy and type. Accuracy is being passed to us in the API via the iOS application, which is dictated by information from the GPS on the phone. Basically accuracy is a value that correlates to the potential error in the latitude/longitude reading. Type is actually a value from a long list defined by Google. We inevitably took out the functionality to allow the user to create a new location due to this one input. The list of types from Google is huge and it would be quite a usability issue on a phone to sift through as a user. We also didn't want to post invalid/bad data to Google by not supplying appropriate types for the locations.

Two important lines to note in this function:
#1 TastyPie requires you to set your new object to the bundle's object. It uses this to return appropriate data on success.
#2 You need to hydrate the bundle, which puts the appropriate information from the object into the resource's fields.

Filtering

def get_object_list(self, request):
    latitude = request.GET.get('latitude')
    longitude = request.GET.get('longitude')

    if latitude and longitude:
        location = "%s,%s" % (latitude, longitude)
    else:
        raise BadRequest("Need latitude and longitude")

    query_name = request.GET.get('query', "")

    query = self._client().query(radius=800, sensor=True, location=location, name=query_name)

    results = []
    for result in query.places:
        result.get_details()
        new_obj = VenueObject(result.reference, result.name, result.formatted_address)
        results.append(new_obj)

    return results

def obj_get_list(self, request=None, **kwargs):
    return self.get_object_list(request)

When requesting a list of information from the API we want to limit the places returned to the consumer based on their current location. We expect that for every request there will be a latitude and longitude GET parameter specified or else we return a BadRequest error. The query also optionally allows for a name parameter, which is sent to Google to limit your results by searching for a specific name.

Once we receive the results from the Google Places API we loop through the entire list and create our VenueObjects so that the API knows how to return the list of information appropriately.

You'll notice above that there is two different functions, get_object_list() and obj_get_list(). From my current understanding of TastyPie, obj_get_list is only called when the API is requesting a list, not a specific resource via Primary Key. For our purposes we will only ever be requesting a list of places so I had both functions share functionality. NOTE: If you will be grabbing individual objects from the API using IDs you should probably separate the search functionality into just the obj_get_list function.

Wrap Up

We've now filled out of all the stub methods for a non-ORM TastyPie resource! There are a few cases here that we have not handled, specifically deleting or editing a resource. For our use case, however, neither of these options are necessary to expose. You could imagine by this point that it would not be very hard to implement with this structure in place.

is a EVP of Tech | Founding Partner at Yeti. He found his passion for technology as a youth, spending his childhood developing games and coding websites. Rudy now resides in the Yeti Cave where he architects Yeti’s system and heads up project production. Follow Rudy on Twitter.

blog comments powered by Disqus
Integrating Google Places API and Django TastyPie https://s3-us-west-1.amazonaws.com/yeti-site-media/uploads/blog/.thumbnails/9415811411_85eef310d8_h.jpg/9415811411_85eef310d8_h-360x0.jpg
Yeti (415) 766-4198 https://s3-us-west-1.amazonaws.com/yeti-site-static/img/yeti-head-blue.png