Kaydet (Commit) 56a57605 authored tarafından Tim Graham's avatar Tim Graham

Refs #25184 -- Removed contrib.gis.geoip per deprecation timeline.

üst a0d16630
"""
This module houses the GeoIP object, a ctypes wrapper for the MaxMind GeoIP(R)
C API (http://www.maxmind.com/app/c). This is an alternative to the GPL
licensed Python GeoIP interface provided by MaxMind.
GeoIP(R) is a registered trademark of MaxMind, LLC of Boston, Massachusetts.
For IP-based geolocation, this module requires the GeoLite Country and City
datasets, in binary format (CSV will not work!). The datasets may be
downloaded from MaxMind at http://www.maxmind.com/download/geoip/database/.
Grab GeoIP.dat.gz and GeoLiteCity.dat.gz, and unzip them in the directory
corresponding to settings.GEOIP_PATH.
"""
__all__ = ['HAS_GEOIP']
try:
from .base import GeoIP, GeoIPException
HAS_GEOIP = True
__all__ += ['GeoIP', 'GeoIPException']
except RuntimeError: # libgeoip.py raises a RuntimeError if no GeoIP library is found
HAS_GEOIP = False
This diff is collapsed.
import os
from ctypes import CDLL
from ctypes.util import find_library
from django.conf import settings
# Creating the settings dictionary with any settings, if needed.
GEOIP_SETTINGS = {key: getattr(settings, key)
for key in ('GEOIP_PATH', 'GEOIP_LIBRARY_PATH', 'GEOIP_COUNTRY', 'GEOIP_CITY')
if hasattr(settings, key)}
lib_path = GEOIP_SETTINGS.get('GEOIP_LIBRARY_PATH')
# The shared library for the GeoIP C API. May be downloaded
# from http://www.maxmind.com/download/geoip/api/c/
if lib_path:
lib_name = None
else:
# TODO: Is this really the library name for Windows?
lib_name = 'GeoIP'
# Getting the path to the GeoIP library.
if lib_name:
lib_path = find_library(lib_name)
if lib_path is None:
raise RuntimeError('Could not find the GeoIP library (tried "%s"). '
'Try setting GEOIP_LIBRARY_PATH in your settings.' % lib_name)
lgeoip = CDLL(lib_path)
# Getting the C `free` for the platform.
if os.name == 'nt':
libc = CDLL('msvcrt')
else:
libc = CDLL(None)
free = libc.free
from ctypes import POINTER, Structure, c_char_p, c_float, c_int, string_at
from django.contrib.gis.geoip.libgeoip import free, lgeoip
# #### GeoIP C Structure definitions ####
class GeoIPRecord(Structure):
_fields_ = [('country_code', c_char_p),
('country_code3', c_char_p),
('country_name', c_char_p),
('region', c_char_p),
('city', c_char_p),
('postal_code', c_char_p),
('latitude', c_float),
('longitude', c_float),
# TODO: In 1.4.6 this changed from `int dma_code;` to
# `union {int metro_code; int dma_code;};`. Change
# to a `ctypes.Union` in to accommodate in future when
# pre-1.4.6 versions are no longer distributed.
('dma_code', c_int),
('area_code', c_int),
('charset', c_int),
('continent_code', c_char_p),
]
geoip_char_fields = [name for name, ctype in GeoIPRecord._fields_ if ctype is c_char_p]
GEOIP_DEFAULT_ENCODING = 'iso-8859-1'
geoip_encodings = {
0: 'iso-8859-1',
1: 'utf8',
}
class GeoIPTag(Structure):
pass
RECTYPE = POINTER(GeoIPRecord)
DBTYPE = POINTER(GeoIPTag)
# #### ctypes function prototypes ####
# GeoIP_lib_version appeared in version 1.4.7.
if hasattr(lgeoip, 'GeoIP_lib_version'):
GeoIP_lib_version = lgeoip.GeoIP_lib_version
GeoIP_lib_version.argtypes = None
GeoIP_lib_version.restype = c_char_p
else:
GeoIP_lib_version = None
# For freeing memory allocated within a record
GeoIPRecord_delete = lgeoip.GeoIPRecord_delete
GeoIPRecord_delete.argtypes = [RECTYPE]
GeoIPRecord_delete.restype = None
# For retrieving records by name or address.
def check_record(result, func, cargs):
if result:
# Checking the pointer to the C structure, if valid pull out elements
# into a dictionary.
rec = result.contents
record = {fld: getattr(rec, fld) for fld, ctype in rec._fields_}
# Now converting the strings to unicode using the proper encoding.
encoding = geoip_encodings[record['charset']]
for char_field in geoip_char_fields:
if record[char_field]:
record[char_field] = record[char_field].decode(encoding)
# Free the memory allocated for the struct & return.
GeoIPRecord_delete(result)
return record
else:
return None
def record_output(func):
func.argtypes = [DBTYPE, c_char_p]
func.restype = RECTYPE
func.errcheck = check_record
return func
GeoIP_record_by_addr = record_output(lgeoip.GeoIP_record_by_addr)
GeoIP_record_by_name = record_output(lgeoip.GeoIP_record_by_name)
# For opening & closing GeoIP database files.
GeoIP_open = lgeoip.GeoIP_open
GeoIP_open.restype = DBTYPE
GeoIP_delete = lgeoip.GeoIP_delete
GeoIP_delete.argtypes = [DBTYPE]
GeoIP_delete.restype = None
# This is so the string pointer can be freed within Python.
class geoip_char_p(c_char_p):
pass
def check_string(result, func, cargs):
if result:
s = string_at(result)
free(result)
else:
s = ''
return s.decode(GEOIP_DEFAULT_ENCODING)
GeoIP_database_info = lgeoip.GeoIP_database_info
GeoIP_database_info.restype = geoip_char_p
GeoIP_database_info.errcheck = check_string
# String output routines.
def string_output(func):
def _err_check(result, func, cargs):
if result:
return result.decode(GEOIP_DEFAULT_ENCODING)
return result
func.restype = c_char_p
func.errcheck = _err_check
return func
GeoIP_country_code_by_addr = string_output(lgeoip.GeoIP_country_code_by_addr)
GeoIP_country_code_by_name = string_output(lgeoip.GeoIP_country_code_by_name)
GeoIP_country_name_by_addr = string_output(lgeoip.GeoIP_country_name_by_addr)
GeoIP_country_name_by_name = string_output(lgeoip.GeoIP_country_name_by_name)
======================
Geolocation with GeoIP
======================
.. module:: django.contrib.gis.geoip
:synopsis: High-level Python interface for MaxMind's GeoIP C library.
.. deprecated:: 1.9
This module is deprecated in favor of :doc:`django.contrib.gis.geoip2
</ref/contrib/gis/geoip2>`, which supports IPv6 and the GeoLite2 database
format.
The :class:`GeoIP` object is a ctypes wrapper for the
`MaxMind GeoIP C API`__. [#]_
In order to perform IP-based geolocation, the :class:`GeoIP` object requires
the GeoIP C library and either the GeoIP `Country`__ or `City`__
datasets in binary format (the CSV files will not work!). These datasets may be
`downloaded from MaxMind`__. Grab the ``GeoLiteCountry/GeoIP.dat.gz`` and
``GeoLiteCity.dat.gz`` files and unzip them in a directory corresponding to what
you set :setting:`GEOIP_PATH` with in your settings. See the example and
reference below for more details.
__ https://www.maxmind.com/app/c
__ https://www.maxmind.com/app/country
__ https://www.maxmind.com/app/city
__ https://www.maxmind.com/download/geoip/database/
Example
=======
Assuming you have the GeoIP C library installed, here is an example of its
usage::
>>> from django.contrib.gis.geoip import GeoIP
>>> g = GeoIP()
>>> g.country('google.com')
{'country_code': 'US', 'country_name': 'United States'}
>>> g.city('72.14.207.99')
{'area_code': 650,
'city': 'Mountain View',
'country_code': 'US',
'country_code3': 'USA',
'country_name': 'United States',
'dma_code': 807,
'latitude': 37.419200897216797,
'longitude': -122.05740356445312,
'postal_code': '94043',
'region': 'CA'}
>>> g.lat_lon('salon.com')
(37.789798736572266, -122.39420318603516)
>>> g.lon_lat('uh.edu')
(-95.415199279785156, 29.77549934387207)
>>> g.geos('24.124.1.80').wkt
'POINT (-95.2087020874023438 39.0392990112304688)'
``GeoIP`` Settings
==================
.. setting:: GEOIP_PATH
``GEOIP_PATH``
--------------
A string specifying the directory where the GeoIP data files are
located. This setting is *required* unless manually specified
with ``path`` keyword when initializing the :class:`GeoIP` object.
.. setting:: GEOIP_LIBRARY_PATH
``GEOIP_LIBRARY_PATH``
----------------------
A string specifying the location of the GeoIP C library. Typically,
this setting is only used if the GeoIP C library is in a non-standard
location (e.g., ``/home/sue/lib/libGeoIP.so``).
.. setting:: GEOIP_COUNTRY
``GEOIP_COUNTRY``
-----------------
The basename to use for the GeoIP country data file.
Defaults to ``'GeoIP.dat'``.
.. setting:: GEOIP_CITY
``GEOIP_CITY``
--------------
The basename to use for the GeoIP city data file.
Defaults to ``'GeoLiteCity.dat'``.
``GeoIP`` API
=============
.. class:: GeoIP(path=None, cache=0, country=None, city=None)
The ``GeoIP`` object does not require any parameters to use the default
settings. However, at the very least the :setting:`GEOIP_PATH` setting
should be set with the path of the location of your GeoIP data sets. The
following initialization keywords may be used to customize any of the
defaults.
=================== =======================================================
Keyword Arguments Description
=================== =======================================================
``path`` Base directory to where GeoIP data is located or the
full path to where the city or country data files
(.dat) are located. Assumes that both the city and
country data sets are located in this directory;
overrides the :setting:`GEOIP_PATH` settings attribute.
``cache`` The cache settings when opening up the GeoIP datasets,
and may be an integer in (0, 1, 2, 4) corresponding to
the ``GEOIP_STANDARD``, ``GEOIP_MEMORY_CACHE``,
``GEOIP_CHECK_CACHE``, and ``GEOIP_INDEX_CACHE``
``GeoIPOptions`` C API settings, respectively.
Defaults to 0 (``GEOIP_STANDARD``).
``country`` The name of the GeoIP country data file. Defaults
to ``GeoIP.dat``. Setting this keyword overrides the
:setting:`GEOIP_COUNTRY` settings attribute.
``city`` The name of the GeoIP city data file. Defaults to
``GeoLiteCity.dat``. Setting this keyword overrides
the :setting:`GEOIP_CITY` settings attribute.
=================== =======================================================
``GeoIP`` Methods
=================
Querying
--------
All the following querying routines may take either a string IP address
or a fully qualified domain name (FQDN). For example, both
``'205.186.163.125'`` and ``'djangoproject.com'`` would be valid query
parameters.
.. method:: GeoIP.city(query)
Returns a dictionary of city information for the given query. Some
of the values in the dictionary may be undefined (``None``).
.. method:: GeoIP.country(query)
Returns a dictionary with the country code and country for the given
query.
.. method:: GeoIP.country_code(query)
Returns only the country code corresponding to the query.
.. method:: GeoIP.country_name(query)
Returns only the country name corresponding to the query.
Coordinate Retrieval
--------------------
.. method:: GeoIP.coords(query)
Returns a coordinate tuple of (longitude, latitude).
.. method:: GeoIP.lon_lat(query)
Returns a coordinate tuple of (longitude, latitude).
.. method:: GeoIP.lat_lon(query)
Returns a coordinate tuple of (latitude, longitude),
.. method:: GeoIP.geos(query)
Returns a :class:`django.contrib.gis.geos.Point` object corresponding to the query.
Database Information
--------------------
.. attribute:: GeoIP.country_info
This property returns information about the GeoIP country database.
.. attribute:: GeoIP.city_info
This property returns information about the GeoIP city database.
.. attribute:: GeoIP.info
This property returns information about all GeoIP databases (both city
and country), and the version of the GeoIP C library (if supported).
GeoIP-Python API compatibility methods
----------------------------------------
These methods exist to ease compatibility with any code using MaxMind's
existing Python API.
.. classmethod:: GeoIP.open(path, cache)
This classmethod instantiates the GeoIP object from the given database path
and given cache setting.
.. method:: GeoIP.region_by_addr(query)
.. method:: GeoIP.region_by_name(query)
.. method:: GeoIP.record_by_addr(query)
.. method:: GeoIP.record_by_name(query)
.. method:: GeoIP.country_code_by_addr(query)
.. method:: GeoIP.country_code_by_name(query)
.. method:: GeoIP.country_name_by_addr(query)
.. method:: GeoIP.country_name_by_name(query)
.. rubric:: Footnotes
.. [#] GeoIP(R) is a registered trademark of MaxMind, LLC of Boston, Massachusetts.
......@@ -22,7 +22,6 @@ of spatially enabled data.
measure
geos
gdal
geoip
geoip2
utils
commands
......
......@@ -263,3 +263,5 @@ these features.
* ``django.contrib.auth.tests.utils.skipIfCustomUser()`` is removed.
* The ``GeoManager`` and ``GeoQuerySet`` classes are removed.
* The ``django.contrib.gis.geoip`` module is removed.
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import socket
import unittest
import warnings
from unittest import skipUnless
from django.conf import settings
from django.contrib.gis.geoip import HAS_GEOIP
from django.contrib.gis.geos import HAS_GEOS, GEOSGeometry
from django.test import ignore_warnings
from django.utils import six
from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.encoding import force_text
if HAS_GEOIP:
from django.contrib.gis.geoip import GeoIP, GeoIPException
from django.contrib.gis.geoip.prototypes import GeoIP_lib_version
# Note: Requires use of both the GeoIP country and city datasets.
# The GEOIP_DATA path should be the only setting set (the directory
# should contain links or the actual database files 'GeoIP.dat' and
# 'GeoLiteCity.dat'.
@skipUnless(
HAS_GEOIP and getattr(settings, "GEOIP_PATH", None),
"GeoIP is required along with the GEOIP_PATH setting."
)
@ignore_warnings(category=RemovedInDjango20Warning)
class GeoIPTest(unittest.TestCase):
addr = '162.242.220.127'
fqdn = 'www.djangoproject.com'
def _is_dns_available(self, domain):
# Naive check to see if there is DNS available to use.
# Used to conditionally skip fqdn geoip checks.
# See #25407 for details.
ErrClass = socket.error if six.PY2 else OSError
try:
socket.gethostbyname(domain)
return True
except ErrClass:
return False
def test01_init(self):
"Testing GeoIP initialization."
g1 = GeoIP() # Everything inferred from GeoIP path
path = settings.GEOIP_PATH
g2 = GeoIP(path, 0) # Passing in data path explicitly.
g3 = GeoIP.open(path, 0) # MaxMind Python API syntax.
for g in (g1, g2, g3):
self.assertTrue(g._country)
self.assertTrue(g._city)
# Only passing in the location of one database.
city = os.path.join(path, 'GeoLiteCity.dat')
cntry = os.path.join(path, 'GeoIP.dat')
g4 = GeoIP(city, country='')
self.assertIsNone(g4._country)
g5 = GeoIP(cntry, city='')
self.assertIsNone(g5._city)
# Improper parameters.
bad_params = (23, 'foo', 15.23)
for bad in bad_params:
with self.assertRaises(GeoIPException):
GeoIP(cache=bad)
if isinstance(bad, six.string_types):
e = GeoIPException
else:
e = TypeError
with self.assertRaises(e):
GeoIP(bad, 0)
def test02_bad_query(self):
"Testing GeoIP query parameter checking."
cntry_g = GeoIP(city='<foo>')
# No city database available, these calls should fail.
with self.assertRaises(GeoIPException):
cntry_g.city('google.com')
with self.assertRaises(GeoIPException):
cntry_g.coords('yahoo.com')
# Non-string query should raise TypeError
with self.assertRaises(TypeError):
cntry_g.country_code(17)
with self.assertRaises(TypeError):
cntry_g.country_name(GeoIP)
def test03_country(self):
"Testing GeoIP country querying methods."
g = GeoIP(city='<foo>')
queries = [self.addr]
if self._is_dns_available(self.fqdn):
queries.append(self.fqdn)
for query in queries:
for func in (g.country_code, g.country_code_by_addr, g.country_code_by_name):
self.assertEqual('US', func(query), 'Failed for func %s and query %s' % (func, query))
for func in (g.country_name, g.country_name_by_addr, g.country_name_by_name):
self.assertEqual('United States', func(query), 'Failed for func %s and query %s' % (func, query))
self.assertEqual({'country_code': 'US', 'country_name': 'United States'},
g.country(query))
@skipUnless(HAS_GEOS, "Geos is required")
def test04_city(self):
"Testing GeoIP city querying methods."
g = GeoIP(country='<foo>')
queries = [self.addr]
if self._is_dns_available(self.fqdn):
queries.append(self.fqdn)
for query in queries:
# Country queries should still work.
for func in (g.country_code, g.country_code_by_addr, g.country_code_by_name):
self.assertEqual('US', func(query))
for func in (g.country_name, g.country_name_by_addr, g.country_name_by_name):
self.assertEqual('United States', func(query))
self.assertEqual({'country_code': 'US', 'country_name': 'United States'},
g.country(query))
# City information dictionary.
d = g.city(query)
self.assertEqual('USA', d['country_code3'])
self.assertEqual('San Antonio', d['city'])
self.assertEqual('TX', d['region'])
self.assertEqual(210, d['area_code'])
geom = g.geos(query)
self.assertIsInstance(geom, GEOSGeometry)
lon, lat = (-98, 29)
lat_lon = g.lat_lon(query)
lat_lon = (lat_lon[1], lat_lon[0])
for tup in (geom.tuple, g.coords(query), g.lon_lat(query), lat_lon):
self.assertAlmostEqual(lon, tup[0], 0)
self.assertAlmostEqual(lat, tup[1], 0)
def test05_unicode_response(self):
"Testing that GeoIP strings are properly encoded, see #16553."
g = GeoIP()
fqdn = "hs-duesseldorf.de"
if self._is_dns_available(fqdn):
d = g.city(fqdn)
self.assertEqual('Düsseldorf', d['city'])
d = g.country('200.26.205.1')
# Some databases have only unaccented countries
self.assertIn(d['country_name'], ('Curaçao', 'Curacao'))
def test_deprecation_warning(self):
with warnings.catch_warnings(record=True) as warns:
warnings.simplefilter('always')
GeoIP()
self.assertEqual(len(warns), 1)
msg = str(warns[0].message)
self.assertIn('django.contrib.gis.geoip is deprecated', msg)
def test_repr(self):
path = settings.GEOIP_PATH
g = GeoIP(path=path)
country_path = g._country_file
city_path = g._city_file
if GeoIP_lib_version:
expected = '<GeoIP [v%(version)s] _country_file="%(country)s", _city_file="%(city)s">' % {
'version': force_text(GeoIP_lib_version()),
'country': country_path,
'city': city_path,
}
else:
expected = '<GeoIP _country_file="%(country)s", _city_file="%(city)s">' % {
'country': country_path,
'city': city_path,
}
self.assertEqual(repr(g), expected)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment