django-layar/layar/__init__.py
2010-03-04 16:43:08 -05:00

217 lines
8.2 KiB
Python

from decimal import Decimal
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``
list of dictionaries with 'label' and 'uri' keys
'''
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, Decimal)):
d['lat'] = int(self.lat*1000000)
if isinstance(self.lon, (float, Decimal)):
d['lon'] = int(self.lon*1000000)
# convert actions dictionary into expected format
if isinstance(self.actions, dict):
raise DeprecationWarning('passing a dictionary for actions is deprecated - order will be lost')
d['actions'] = [{'label':k, 'uri':v} for k,v in self.actions.iteritems()]
elif isinstance(self.actions, list):
pass
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``
This function is passed latitude, longitude, radius,
radio_option, search_query, and slider_value parameters.
radio_option, search_query, and slider_value may be None
depending on how you've configured your widgets on layar.com
**Note:** It is strongly recommended that you make this function
accept **kwargs for maximum compatibility
``poi_from_LAYERNAME_item(item)``
convert an item of whatever type is returned by
:func:`get_LAYARNAME_queryset` into a ``POI`` object
This separation allows LayarView to handle pagination correctly.
Your derived class can also set a number of options. The defaults
should be suitable for most purposes as Layar doesn't display more
than 50 points.
``results_per_page``
controls the number of results returned at once (default: 15)
``max_results``
controls the maximum number of results across all pages (default: 50)
``verify_hash``
set to False to disable hash verification (useful for testing)
'''
results_per_page = 15
max_results = 50
custom_radius = 1000
verify_hash = True
def __init__(self):
self.developer_key = settings.LAYAR_DEVELOPER_KEY
def __call__(self, request):
try:
# parameters from http://layar.pbworks.com/GetPointsOfInterest
# required parameters
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'])
# optional
accuracy = request.GET.get('accuracy')
if accuracy:
accuracy = int(accuracy)
radius = request.GET.get('radius')
if radius:
radius = int(radius)
alt = request.GET.get('alt')
if alt:
alt = int(alt)
page = int(request.GET.get('pageKey', 0))
# user defined UI elements
radio_option = request.GET.get('RADIOLIST')
search = request.GET.get('SEARCHBOX')
search2 = request.GET.get('SEARCHBOX_2')
search3 = request.GET.get('SEARCHBOX_3')
slider = request.GET.get('CUSTOM_SLIDER')
slider2 = request.GET.get('CUSTOM_SLIDER_2')
slider3 = request.GET.get('CUSTOM_SLIDER_3')
checkboxes = request.GET.get('CHECKBOXLIST')
if checkboxes:
checkboxes = checkboxes.split(',')
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,
search_query2=search2, search_query3=search3,
slider_value=slider, slider_value2=slider2,
slider_value3=slider3, checkboxes=checkboxes)
qs = qs[: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]
# if radius wasn't sent pass back the radius used
if not radius:
layar_response['radius'] = self.custom_radius
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')