django-layar/layar/__init__.py
2009-10-23 11:32:56 -04:00

172 lines
6.3 KiB
Python

from django.conf import settings
from django.http import HttpResponse, HttpResponseBadRequest
from django.utils.hashcompat import sha_constructor as sha1
from django.utils import simplejson as json
class LayarException(Exception):
''' Layar exception - takes a code (20-29) to return to Layar'''
def __init__(self, code, message):
self.code = code
self.message = message
class POI(object):
'''
Object representing Layar Points of Interest
Layar has some specific requirements (eg. that lat/longs are converted
to fixed point) that are taken care of when converting these objects
to JSON. String lengths mentioned below are recommended, strings will
not be truncated on the server.
Required fields:
``id``
unique id for this POI
``lat``
latitude of POI
``lon``
longitude of POI
``title``
Name of POI, displayed in large font. (<= 60 chars)
Optional fields:
``imageURL``
image to display when POI is selected
``line2, line3, line4``
additional lines of detail (use special token %distance% to
display distance to POI) (<= 35 chars)
``type``
numerical type (0-3), can set meaning when publishing Layar
``attribution``
bottom line of display, shown in small font (<= 45 chars)
``actions``
dictionary mapping names of actions to URIs
'''
def __init__(self, id, lat, lon, title, actions=None, image_url=None,
line2=None, line3=None, line4=None, type=0, attribution=None):
self.id = str(id)
self.lat = lat
self.lon = lon
self.title = title # recommended max len 60
self.imageURL = image_url
self.line2 = line2 # recommended max len 35
self.line3 = line3
self.line4 = line4
self.type = type # must be 0..3
self.attribution = attribution # recommended max len 45
self.actions = actions
def to_dict(self):
d = dict(self.__dict__)
# do lat/long conversion
if isinstance(self.lat, float):
d['lat'] = int(self.lat*1000000)
if isinstance(self.lon, float):
d['lon'] = int(self.lon*1000000)
# convert actions dictionary into expected format
if self.actions:
d['actions'] = [{'label':k, 'uri':v} for k,v in self.actions.iteritems()]
else:
d['actions'] = []
return d
class LayarView(object):
'''
Class-based generic view for creating a Layar endpoint.
To add a layar it is necessary to write two functions:
``get_LAYERNAME_queryset``
return a queryset of objects to be used to build the layer
``poi_from_LAYERNAME_item``
convert an item of whatever type is returned by
get_LAYARNAME_queryset into a ``POI`` object
This separation allows LayarView to handle pagination correctly.
Options:
``results_per_page``
defaults to the Layar-recommended 15
``max_results``
defaults to the Layar-recommended 50
``verify_hash``
set to False to disable hash verification (useful for testing)
'''
results_per_page = 15
max_results = 50
verify_hash = True
def __init__(self):
self.developer_key = settings.LAYAR_DEVELOPER_KEY
def __call__(self, request):
try:
user_id = request.GET['userId']
developer_id = request.GET['developerId']
developer_hash = request.GET['developerHash']
timestamp = request.GET['timestamp']
layer_name = request.GET['layerName']
lat = float(request.GET['lat'])
lon = float(request.GET['lon'])
accuracy = int(request.GET['accuracy'])
radius = int(request.GET['radius'])
radio_option = request.GET.get('RADIOLIST')
search = request.GET.get('SEARCHBOX')
slider = request.GET.get('CUSTOM_SLIDER')
page = int(request.GET.get('pageKey', 0))
except KeyError, e:
return HttpResponseBadRequest('missing required parameter: %s' % e)
layar_response = dict(hotspots=[], layer=layer_name, errorCode=0,
errorString='ok', nextPageKey=None, morePages=False)
try:
# verify hash
if self.verify_hash:
key = self.developer_key + timestamp
if sha1(key).hexdigest() != developer_hash:
raise LayarException(20, 'Bad developerHash')
# get ``max_results`` items from queryset
try:
qs_func = getattr(self, 'get_%s_queryset' % layer_name)
except AttributeError:
raise LayarException(21, 'no such layer: %s' % layer_name)
qs = qs_func(latitude=lat, longitude=lon, radius=radius,
radio_option=radio_option, search_query=search,
slider_value=slider)[:self.max_results]
# do pagination if results_per_page is set
if self.results_per_page:
start_index = self.results_per_page * page
end_index = start_index + self.results_per_page
# if there are more pages, indicate that in response
if end_index < qs.count()-1:
layar_response['morePages'] = True
layar_response['nextPageKey'] = str(page+1)
qs = qs[start_index:end_index]
# convert queryset into POIs
try:
poi_func = getattr(self, 'poi_from_%s_item' % layer_name)
except AttributeError:
raise LayarException(21, 'no such layer: %s' % layer_name)
pois = [poi_func(item) for item in qs]
layar_response['hotspots'] = [poi.to_dict() for poi in pois]
except LayarException, e:
layar_response['errorCode'] = e.code
layar_response['errorString'] = e.message
content = json.dumps(layar_response)
return HttpResponse(content,
content_type='application/javascript; charset=utf-8')