django-zipdistance/zipdistance/models.py

75 lines
2.3 KiB
Python

from django.db import models
from django.db.models import Aggregate
from django.db.models.sql.aggregates import Aggregate as AggregateImpl
class DistanceFromImpl(AggregateImpl):
sql_function = ''
is_computed = True
is_ordinal = True
sql_template = ('3959 * acos( cos( radians(%(t_lat)f) ) * cos( radians( latitude ) ) * '
'cos( radians( longitude ) - radians(%(t_lon)f) ) + sin( radians(%(t_lat)f) ) * '
'sin( radians( latitude ) ) )')
def __init__(self, col, target, **extra):
self.col = col
self.target = target
self.extra = extra
def _default_alias(self):
return '%s__%s' % (str(self.target), self.__class__.__name__.lower())
default_alias = property(_default_alias)
def add_to_query(self, query, alias, col, source, is_summary):
super(DistanceFrom, self).__init__(col, source, is_summary, **self.extra)
query.aggregate_select[alias] = self
def as_sql(self, qn, connection):
"Return the aggregate, rendered as SQL."
return self.sql_template % { 't_lon': self.target.longitude,
't_lat': self.target.latitude }
class DistanceFrom(Aggregate):
name="DistanceFromImpl"
def add_to_query(self, query, alias, col, source, is_summary):
aggregate = DistanceFromImpl(col, source=source, is_summary=is_summary, **self.extra)
query.aggregates[alias] = aggregate
class ZipDistanceManager(models.Manager):
def distance_from(self, target, limit = 0):
qs = self.annotate(distance = DistanceFrom('zipcode', target = target))
if bool(limit):
qs = qs.filter(distance__lte = float(limit))
qs = qs.order_by('distance')
return qs
class ZipDistance(models.Model):
state = models.CharField(max_length = 2)
zipcode = models.CharField(max_length = 5, unique = True)
latitude = models.FloatField()
longitude = models.FloatField()
objects = ZipDistanceManager()
class Meta:
ordering = ['zipcode']
def __unicode__(self):
return '%s:%s' % (self.state, self.zipcode)
def distance_between(self, other):
return self.__class__.objects.distance_from(self).get(zipcode = other.zipcode).distance