Country detection in Django
On my Django website mactactic.com, I needed to sus out which country a visitor was coming from.
I found MaxMind (which is a commercial product, but has a GPL’d library in C and Python), and it seemed okay… But it was beyond painful to install in a shared hosting environment. GNU configure was responsible for most of the fun, but it just felt bad & hacky. It was a huge package too, for something that’s really pretty basic.
Some Google and i stumbled across ip2cc.py, which was a totally python-native ip address to country code system. It’s got the added bonus of being independant of maxmind. It actually FTPs a list of IP delegations from IANA manually and builds it’s own database. Great!
Few issues, the ip2cc.py program is a standalone, and needed to be converted into a module. Also, it’s exception handling wasn’t as wide as I wanted in a web application, so i changed a few bits and pieces as well as converted it into a module. So now it’s error reporting is much less than adequate, it will just return a country code of ‘??’ where it can’t find it’s database file, or under any other error condition.
The country codes reported are the ones directly from the IANA database.
ip2cc (which you should download if you’re using this django hack) includes update.py, which is responsible for fetching the database. You should probably put update.py in a cron job if you use this in production once a week or so.
Next step was to integrate it with Django. I’m a slight django neophyte, but the long and the short of it:
- Download ip2cc, run update.py and then test ip2cc
- Check your django views in your application are using RequestContext - even if you’re using render_to_response (check the Note below)
- Add the Context processor that’s in the blockquote below to your settings.py
- Try out {{country}} in your template
OK, so here’s the code:
ip2ccm.py
import re, struct
is_IP = re.compile('^%s$' % r'.'.join([r'(?:(?:2[0-4]|1d|[1-9])?d|25[0-5])']*4)).match
class CountryByIP:
def __init__(self, filename):
self.fp = open(filename, 'rb')
def __getitem__(self, ip):
offset = 0
fp = self.fp
for part in ip.split('.'):
start = offset+int(part)*4
fp.seek(start)
value = fp.read(4)
assert len(value)==4
if value[:2]=='xFFxFF':
if value[2:]=='x00x00':
raise KeyError(ip)
else:
return value[2:]
offset = struct.unpack('!I', value)[0]
raise RuntimeError('ip2cc database is briken') # must never reach here
import sys, os
def lookup(ip):
db_file = '/home/myproject/ip2cc.db' ### SET THIS UP
try:
db = CountryByIP(db_file)
except IOError, exc:
import errno
if exc.errno==errno.ENOENT:
return "??"
else:
return "??"
try:
cc = db[ip]
except:
return "??"
else:
return cc
Add this (or create a) context_processors.py
import yourproject.ip2ccm
def country(request):
from django.conf import settings
ip = request.META.get('REMOTE_ADDR')
country = ip2ccm.lookup(ip)
context_extras = {}
context_extras['country'] = country
return context_extras
And into your settings.py:
TEMPLATE_CONTEXT_PROCESSORS = (
'yourproject.context_processors.country',
)
So, give it a bash, and let me know if it works for you, or if not.