Where is my user? Part 1, GeoIP

There are a a few techniques we can use to make our apps more “magical” for our end users – showing them things that we have reason to believe may be spatially relevant to them. The most reliable but least accurate of these techniques is the combination of a hidden GeoDjango battery with a free (as in beer) data set from MaxMind.

GeoDjango includes ctypes bindings against their GeoIP C API to give us an easier interface to the data. You’ll need to install the C API and download a copy of GeoLiteCity.dat.gz to your server and set up the correct paths as mentioned in the docs.

Once that’s done it’s very simple to interact with.

>>> from django.contrib.gis.utils.geoip import GeoIP
>>> GeoIP().city("amazon.com")
{'city': 'Seattle', 'region': 'WA', 'area_code': 206, 'longitude': -122.32839965820312, 'country_code3': 'USA', 'latitude': 47.60260009765625, 'postal_code': '98104', 'dma_code': 819, 'country_code': 'US', 'country_name': 'United States'}

For the ultimate in handiness I’ve turned it into a context processor. Context processors don’t always get the limelight they deserve, they’re insanely handy for things you want on EVERY view (well, every view you remember to use a RequestContext for) like MEDIA_URL. In our case, we’re going to add to every template’s context the location of our user.

And we’re going to do it with less than 10 lines of code (though a bit more for comments and spacing)

from django.contrib.gis.utils.geoip import GeoIP

def addgeoip(request):
    # check x-forwarded-for first in case there's a reverse proxy in front of the webserver
    if request.META.has_key('HTTP_X_FORWARDED_FOR'):
        ip = request.META['HTTP_X_FORWARDED_FOR']
    else:
        ip = request.META.get('REMOTE_ADDR', False)

    if ip:
        return {'geoip': GeoIP().city(ip)}
    else:
        return {}

If you have a moderate or greater traffic site, you probably run behind a reverse proxy like perlbal, nginx or Varnish – and your REMOTE_ADDR meta headers are incorrect as a result of this. You’ve seen it in your comments or other places that log IP address and probably heard of HTTP_X_FORWARDED_FOR. This HTTP header is set by these reverse proxies so we can see in our code who their client is. The first four lines here determine if we need to deal with this. NOTE: Django 1.3 includes a Reverse proxy middleware  that does what the first four lines of this context processor do if you enable it. Please note it’s security notice as it applies here – if you’re not behind a reverse proxy, listening to HTTP_X_FORWARDED_FOR will allow users to spoof their IPs.

Drop this code anywhere on your PYTHONPATH (in a utils.py or processors.py might be nice?) and set settings.TEMPLATE_CONTEXT_PROCESSORS to include it. But be careful you include the defaults too – you don’t want to wipe out the defaults so copy/paste to make sure you don’t miss any.

If you don’t want to add RequestContext to your view, or need it inside the view and not just the template, it’s a normal function – call it with your request object. If you’re not in a view, don’t use the processor and just call it normally (unless you want to go to the effort of mocking the request but that seems silly.)

So there we are – we know a decent amount of reasonably detailed info on our users’ location and can do pre-filtering of data.

If you want better accuracy, you can buy better data. But know that it’s never going to be perfect, the location they will have is the location the telecom provider says. The IP address info for my grandparents’ home is about 80 miles east, has been for years. My iPhone? Well, it says I’m in California but I’m actually in Kansas. So this method is a fall-back at best.

Leave a Reply

Your email address will not be published. Required fields are marked *