Switch to using ipstack.com's free API for geoip lookup. Requires setting IPSTACK_ACCESS_KEY in settings.py now. Fixed #90
This commit is contained in:
parent
316a047f14
commit
501918da77
5 changed files with 131 additions and 34 deletions
115
events/ipstack.py
Normal file
115
events/ipstack.py
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
from collections import OrderedDict
|
||||||
|
import requests
|
||||||
|
from django.conf import settings
|
||||||
|
from geocoder.base import OneResult, MultipleResultsQuery
|
||||||
|
|
||||||
|
IPSTACK_URL = 'http://api.ipstack.com/{0}?access_key={1}&format=json&legacy=1'
|
||||||
|
RESULT_CACHE = OrderedDict()
|
||||||
|
CACHE_SIZE = getattr(settings, 'IPSTACK_CACHE_SIZE', 1000)
|
||||||
|
|
||||||
|
|
||||||
|
class IPStackResult(OneResult):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lat(self):
|
||||||
|
return self.raw.get('latitude')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lng(self):
|
||||||
|
return self.raw.get('longitude')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def latlng(self):
|
||||||
|
if self.ok:
|
||||||
|
return [self.lat, self.lng]
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def address(self):
|
||||||
|
if self.city:
|
||||||
|
return u'{0}, {1} {2}'.format(self.city, self.state, self.country)
|
||||||
|
elif self.state:
|
||||||
|
return u'{0}, {1}'.format(self.state, self.country)
|
||||||
|
elif self.country:
|
||||||
|
return u'{0}'.format(self.country)
|
||||||
|
return u''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def postal(self):
|
||||||
|
zip_code = self.raw.get('zip_code')
|
||||||
|
postal_code = self.raw.get('postal_code')
|
||||||
|
if zip_code:
|
||||||
|
return zip_code
|
||||||
|
if postal_code:
|
||||||
|
return postal_code
|
||||||
|
|
||||||
|
@property
|
||||||
|
def city(self):
|
||||||
|
return self.raw.get('city')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
return self.raw.get('region')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def region_code(self):
|
||||||
|
return self.raw.get('region_code')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def country(self):
|
||||||
|
return self.raw.get('country_name')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def country_code3(self):
|
||||||
|
return self.raw.get('country_code3')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def continent(self):
|
||||||
|
return self.raw.get('continent')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def timezone(self):
|
||||||
|
return self.raw.get('timezone')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def area_code(self):
|
||||||
|
return self.raw.get('area_code')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dma_code(self):
|
||||||
|
return self.raw.get('dma_code')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def offset(self):
|
||||||
|
return self.raw.get('offset')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def organization(self):
|
||||||
|
return self.raw.get('organization')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ip(self):
|
||||||
|
return self.raw.get('ip')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def time_zone(self):
|
||||||
|
return self.raw.get('time_zone')
|
||||||
|
|
||||||
|
|
||||||
|
def get_ipstack_geocoder(ip):
|
||||||
|
if ip in RESULT_CACHE:
|
||||||
|
return RESULT_CACHE[ip]
|
||||||
|
ipstack_key = getattr(settings, 'IPSTACK_ACCESS_KEY', None)
|
||||||
|
if ipstack_key is None:
|
||||||
|
raise Exception("You must define IPSTACK_ACCESS_KEY in your setting to use ipstack.py geocoding")
|
||||||
|
call_url = IPSTACK_URL.format(ip, ipstack_key)
|
||||||
|
print("Calling ipstack: {0}".format(call_url))
|
||||||
|
session = requests.Session()
|
||||||
|
response = session.get(call_url)
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception("Call to ipstack.com returned status code {0}".format(response.status_code))
|
||||||
|
result = IPStackResult(response.json())
|
||||||
|
RESULT_CACHE[ip] = result
|
||||||
|
if len(RESULT_CACHE) > CACHE_SIZE:
|
||||||
|
RESULT_CACHE.popitem(last=False) # Discard the oldest entry
|
||||||
|
return result
|
|
@ -2,14 +2,15 @@ from django.utils import timezone
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from .models.locale import City
|
from .models.locale import City
|
||||||
|
from .ipstack import get_ipstack_geocoder
|
||||||
|
|
||||||
import math
|
import math
|
||||||
import pytz
|
import pytz
|
||||||
import datetime
|
|
||||||
import geocoder
|
import geocoder
|
||||||
|
|
||||||
KM_PER_DEGREE_LAT = 110.574
|
KM_PER_DEGREE_LAT = 110.574
|
||||||
KM_PER_DEGREE_LNG = 111.320 # At the equator
|
KM_PER_DEGREE_LNG = 111.320 # At the equator
|
||||||
|
DEFAULT_NEAR_DISTANCE = 100 # kilometeres
|
||||||
|
|
||||||
class TimezoneChoices():
|
class TimezoneChoices():
|
||||||
|
|
||||||
|
@ -29,12 +30,12 @@ def get_geoip(request):
|
||||||
client_ip = get_client_ip(request)
|
client_ip = get_client_ip(request)
|
||||||
if client_ip == '127.0.0.1' or client_ip == 'localhost':
|
if client_ip == '127.0.0.1' or client_ip == 'localhost':
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
client_ip = getattr(settings, 'DEBUG_IP', '8.8.8.8') # Try Google's server
|
client_ip = getattr(settings, 'DEBUG_IP', '8.8.8.8') # Try Debug ip
|
||||||
print("Client is localhost, using %s for geoip instead" % client_ip)
|
print("Client is localhost, using %s for geoip instead" % client_ip)
|
||||||
else:
|
else:
|
||||||
raise Exception("Client is localhost")
|
raise Exception("Client is localhost")
|
||||||
|
|
||||||
g = geocoder.freegeoip(client_ip)
|
g = get_ipstack_geocoder(client_ip)
|
||||||
return g
|
return g
|
||||||
|
|
||||||
def get_bounding_box(center, radius):
|
def get_bounding_box(center, radius):
|
||||||
|
@ -101,3 +102,5 @@ def get_nearest_city(ll, max_distance=100):
|
||||||
else:
|
else:
|
||||||
return sorted(nearby_cities, key=lambda city: city_distance_from(ll, city))[0]
|
return sorted(nearby_cities, key=lambda city: city_distance_from(ll, city))[0]
|
||||||
return city
|
return city
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -169,6 +169,7 @@ STATIC_URL = '/static/'
|
||||||
MEDIA_ROOT = './media/'
|
MEDIA_ROOT = './media/'
|
||||||
MEDIA_URL = '/media/'
|
MEDIA_URL = '/media/'
|
||||||
|
|
||||||
|
IPSTACK_ACCESS_KEY=None
|
||||||
GOOGLE_ANALYTICS_ID=None
|
GOOGLE_ANALYTICS_ID=None
|
||||||
GOOGLE_MAPS_API_KEY=None
|
GOOGLE_MAPS_API_KEY=None
|
||||||
SOCIAL_AUTH_GITHUB_KEY=None
|
SOCIAL_AUTH_GITHUB_KEY=None
|
||||||
|
@ -197,6 +198,7 @@ SETTINGS_EXPORT = [
|
||||||
'SOCIAL_AUTH_FACEBOOK_KEY',
|
'SOCIAL_AUTH_FACEBOOK_KEY',
|
||||||
'SOCIAL_AUTH_TWITTER_KEY',
|
'SOCIAL_AUTH_TWITTER_KEY',
|
||||||
'SOCIAL_AUTH_LINKEDIN_KEY',
|
'SOCIAL_AUTH_LINKEDIN_KEY',
|
||||||
|
'IPSTACK_ACCESS_KEY',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Make django messages framework use Bootstrap's alert style classes
|
# Make django messages framework use Bootstrap's alert style classes
|
||||||
|
|
|
@ -4,17 +4,18 @@ from django.utils import timezone
|
||||||
|
|
||||||
from model_mommy import mommy
|
from model_mommy import mommy
|
||||||
import mock
|
import mock
|
||||||
import geocoder
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from events.ipstack import get_ipstack_geocoder
|
||||||
from events.models import Event, Place, Attendee, UserProfile
|
from events.models import Event, Place, Attendee, UserProfile
|
||||||
# Create your tests here.
|
# Create your tests here.
|
||||||
|
|
||||||
def mock_get_geoip(latlng=(0.0, 0.0)):
|
def mock_get_geoip(latitude=0.0, longitude=0.0):
|
||||||
def get_geoip(request):
|
def get_geoip(request):
|
||||||
g = geocoder.ip('8.8.8.8')
|
g = get_ipstack_geocoder('8.8.8.8')
|
||||||
g.latlng = latlng
|
g.raw['latitude'] = latitude
|
||||||
|
g.raw['longitude'] = longitude
|
||||||
return g
|
return g
|
||||||
return get_geoip
|
return get_geoip
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ class EventDisplayTests(TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
|
|
||||||
@mock.patch("events.location.get_geoip", mock_get_geoip((0.0, 0.0)))
|
@mock.patch("events.location.get_geoip", mock_get_geoip(latitude=0.0, longitude=0.0))
|
||||||
def test_events_list(self):
|
def test_events_list(self):
|
||||||
place = mommy.make(Place, name="Test Place", latitude=0.0, longitude=0.0)
|
place = mommy.make(Place, name="Test Place", latitude=0.0, longitude=0.0)
|
||||||
event = mommy.make(Event, name="Test Event", place=place, start_time=timezone.now() + datetime.timedelta(days=1), end_time=timezone.now() + datetime.timedelta(days=2))
|
event = mommy.make(Event, name="Test Event", place=place, start_time=timezone.now() + datetime.timedelta(days=1), end_time=timezone.now() + datetime.timedelta(days=2))
|
||||||
|
@ -36,7 +37,7 @@ class EventDisplayTests(TestCase):
|
||||||
response = c.get(resolve_url('all-events'))
|
response = c.get(resolve_url('all-events'))
|
||||||
assert(response.status_code == 200)
|
assert(response.status_code == 200)
|
||||||
|
|
||||||
@mock.patch("events.location.get_geoip", mock_get_geoip(None))
|
@mock.patch("events.location.get_geoip", mock_get_geoip(latitude=None, longitude=None))
|
||||||
def test_events_list_no_geoip(self):
|
def test_events_list_no_geoip(self):
|
||||||
place = mommy.make(Place, name="Test Place", latitude=0.0, longitude=0.0)
|
place = mommy.make(Place, name="Test Place", latitude=0.0, longitude=0.0)
|
||||||
event = mommy.make(Event, name="Test Event", place=place, start_time=timezone.now() + datetime.timedelta(days=1), end_time=timezone.now() + datetime.timedelta(days=2))
|
event = mommy.make(Event, name="Test Event", place=place, start_time=timezone.now() + datetime.timedelta(days=1), end_time=timezone.now() + datetime.timedelta(days=2))
|
||||||
|
|
|
@ -2,12 +2,9 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
import datetime
|
|
||||||
import simplejson
|
|
||||||
import geocoder
|
|
||||||
import math
|
import math
|
||||||
import traceback
|
|
||||||
|
|
||||||
|
from events.location import get_geoip, get_client_ip
|
||||||
from .new_user import *
|
from .new_user import *
|
||||||
|
|
||||||
KM_PER_DEGREE_LAT = 110.574
|
KM_PER_DEGREE_LAT = 110.574
|
||||||
|
@ -15,19 +12,6 @@ KM_PER_DEGREE_LNG = 111.320 # At the equator
|
||||||
DEFAULT_NEAR_DISTANCE = 100 # kilometeres
|
DEFAULT_NEAR_DISTANCE = 100 # kilometeres
|
||||||
|
|
||||||
|
|
||||||
def get_geoip(request):
|
|
||||||
client_ip = get_client_ip(request)
|
|
||||||
if client_ip == '127.0.0.1' or client_ip == 'localhost':
|
|
||||||
if settings.DEBUG:
|
|
||||||
client_ip = '8.8.8.8' # Try Google's server
|
|
||||||
print("Client is localhost, using 8.8.8.8 for geoip instead")
|
|
||||||
else:
|
|
||||||
raise Exception("Client is localhost")
|
|
||||||
|
|
||||||
g = geocoder.ip(client_ip)
|
|
||||||
return g
|
|
||||||
|
|
||||||
|
|
||||||
def get_nearby_teams(request, near_distance=DEFAULT_NEAR_DISTANCE):
|
def get_nearby_teams(request, near_distance=DEFAULT_NEAR_DISTANCE):
|
||||||
g = get_geoip(request)
|
g = get_geoip(request)
|
||||||
if g.latlng is None or g.latlng[0] is None or g.latlng[1] is None:
|
if g.latlng is None or g.latlng[0] is None or g.latlng[1] is None:
|
||||||
|
@ -46,13 +30,5 @@ def get_nearby_teams(request, near_distance=DEFAULT_NEAR_DISTANCE):
|
||||||
return Team.objects.none()
|
return Team.objects.none()
|
||||||
|
|
||||||
|
|
||||||
def get_client_ip(request):
|
|
||||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
|
||||||
if x_forwarded_for:
|
|
||||||
ip = x_forwarded_for.split(',')[0]
|
|
||||||
else:
|
|
||||||
ip = request.META.get('REMOTE_ADDR')
|
|
||||||
return ip
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue