diff --git a/events/ipstack.py b/events/ipstack.py new file mode 100644 index 0000000..470871c --- /dev/null +++ b/events/ipstack.py @@ -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 diff --git a/events/location.py b/events/location.py index 164c3ef..44461b3 100644 --- a/events/location.py +++ b/events/location.py @@ -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 + + diff --git a/get_together/settings.py b/get_together/settings.py index b3b10e4..4c3ad0f 100644 --- a/get_together/settings.py +++ b/get_together/settings.py @@ -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 diff --git a/get_together/tests/events.py b/get_together/tests/events.py index 89577ec..67b7683 100644 --- a/get_together/tests/events.py +++ b/get_together/tests/events.py @@ -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)) diff --git a/get_together/views/utils.py b/get_together/views/utils.py index 02ca5e5..0d08c88 100644 --- a/get_together/views/utils.py +++ b/get_together/views/utils.py @@ -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 -