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:
Michael Hall 2018-06-24 13:07:28 -04:00
parent 316a047f14
commit 501918da77
5 changed files with 131 additions and 34 deletions

115
events/ipstack.py Normal file
View 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

View file

@ -2,14 +2,15 @@ from django.utils import timezone
from django.conf import settings
from .models.locale import City
from .ipstack import get_ipstack_geocoder
import math
import pytz
import datetime
import geocoder
KM_PER_DEGREE_LAT = 110.574
KM_PER_DEGREE_LNG = 111.320 # At the equator
DEFAULT_NEAR_DISTANCE = 100 # kilometeres
class TimezoneChoices():
@ -29,12 +30,12 @@ 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 = 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)
else:
raise Exception("Client is localhost")
g = geocoder.freegeoip(client_ip)
g = get_ipstack_geocoder(client_ip)
return g
def get_bounding_box(center, radius):
@ -101,3 +102,5 @@ def get_nearest_city(ll, max_distance=100):
else:
return sorted(nearby_cities, key=lambda city: city_distance_from(ll, city))[0]
return city

View file

@ -169,6 +169,7 @@ STATIC_URL = '/static/'
MEDIA_ROOT = './media/'
MEDIA_URL = '/media/'
IPSTACK_ACCESS_KEY=None
GOOGLE_ANALYTICS_ID=None
GOOGLE_MAPS_API_KEY=None
SOCIAL_AUTH_GITHUB_KEY=None
@ -197,6 +198,7 @@ SETTINGS_EXPORT = [
'SOCIAL_AUTH_FACEBOOK_KEY',
'SOCIAL_AUTH_TWITTER_KEY',
'SOCIAL_AUTH_LINKEDIN_KEY',
'IPSTACK_ACCESS_KEY',
]
# Make django messages framework use Bootstrap's alert style classes

View file

@ -4,17 +4,18 @@ from django.utils import timezone
from model_mommy import mommy
import mock
import geocoder
import datetime
from django.contrib.auth.models import User
from events.ipstack import get_ipstack_geocoder
from events.models import Event, Place, Attendee, UserProfile
# 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):
g = geocoder.ip('8.8.8.8')
g.latlng = latlng
g = get_ipstack_geocoder('8.8.8.8')
g.raw['latitude'] = latitude
g.raw['longitude'] = longitude
return g
return get_geoip
@ -26,7 +27,7 @@ class EventDisplayTests(TestCase):
def tearDown(self):
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):
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))
@ -36,7 +37,7 @@ class EventDisplayTests(TestCase):
response = c.get(resolve_url('all-events'))
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):
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))

View file

@ -2,12 +2,9 @@ from django.utils.translation import ugettext_lazy as _
from django.conf import settings
import datetime
import simplejson
import geocoder
import math
import traceback
from events.location import get_geoip, get_client_ip
from .new_user import *
KM_PER_DEGREE_LAT = 110.574
@ -15,19 +12,6 @@ KM_PER_DEGREE_LNG = 111.320 # At the equator
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):
g = get_geoip(request)
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()
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