diff --git a/django/contrib/gis/geoip2/base.py b/django/contrib/gis/geoip2.py similarity index 81% rename from django/contrib/gis/geoip2/base.py rename to django/contrib/gis/geoip2.py index d07f79dd2526..898f5d596da8 100644 --- a/django/contrib/gis/geoip2/base.py +++ b/django/contrib/gis/geoip2.py @@ -1,15 +1,35 @@ +""" +This module houses the GeoIP2 object, a wrapper for the MaxMind GeoIP2(R) +Python API (https://geoip2.readthedocs.io/). This is an alternative to the +Python GeoIP2 interface provided by MaxMind. + +GeoIP(R) is a registered trademark of MaxMind, Inc. + +For IP-based geolocation, this module requires the GeoLite2 Country and City +datasets, in binary format (CSV will not work!). The datasets may be +downloaded from MaxMind at https://dev.maxmind.com/geoip/geoip2/geolite2/. +Grab GeoLite2-Country.mmdb.gz and GeoLite2-City.mmdb.gz, and unzip them in the +directory corresponding to settings.GEOIP_PATH. +""" + import socket import warnings -import geoip2.database - from django.conf import settings from django.core.exceptions import ValidationError from django.core.validators import validate_ipv46_address from django.utils._os import to_path from django.utils.deprecation import RemovedInDjango60Warning -from .resources import City, Country +__all__ = ["HAS_GEOIP2"] + +try: + import geoip2.database +except ImportError: + HAS_GEOIP2 = False +else: + HAS_GEOIP2 = True + __all__ += ["GeoIP2", "GeoIP2Exception"] # Creating the settings dictionary with any settings, if needed. GEOIP_SETTINGS = { @@ -182,7 +202,22 @@ def city(self, query): may be undefined (None). """ enc_query = self._check_query(query, city=True) - return City(self._city.city(enc_query)) + response = self._city.city(enc_query) + region = response.subdivisions[0] if response.subdivisions else None + return { + "city": response.city.name, + "continent_code": response.continent.code, + "continent_name": response.continent.name, + "country_code": response.country.iso_code, + "country_name": response.country.name, + "dma_code": response.location.metro_code, + "is_in_european_union": response.country.is_in_european_union, + "latitude": response.location.latitude, + "longitude": response.location.longitude, + "postal_code": response.postal.code, + "region": region.iso_code if region else None, + "time_zone": response.location.time_zone, + } def country_code(self, query): "Return the country code for the given IP Address or FQDN." @@ -200,7 +235,11 @@ def country(self, query): """ # Returning the country code and name enc_query = self._check_query(query, city_or_country=True) - return Country(self._country_or_city(enc_query)) + response = self._country_or_city(enc_query) + return { + "country_code": response.country.iso_code, + "country_name": response.country.name, + } def coords(self, query, ordering=("longitude", "latitude")): warnings.warn( @@ -230,4 +269,9 @@ def geos(self, query): @classmethod def open(cls, full_path, cache): + warnings.warn( + "GeoIP2.open() is deprecated. Use GeoIP2() instead.", + RemovedInDjango60Warning, + stacklevel=2, + ) return GeoIP2(full_path, cache) diff --git a/django/contrib/gis/geoip2/__init__.py b/django/contrib/gis/geoip2/__init__.py deleted file mode 100644 index 71b71f68dbaf..000000000000 --- a/django/contrib/gis/geoip2/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -This module houses the GeoIP2 object, a wrapper for the MaxMind GeoIP2(R) -Python API (https://geoip2.readthedocs.io/). This is an alternative to the -Python GeoIP2 interface provided by MaxMind. - -GeoIP(R) is a registered trademark of MaxMind, Inc. - -For IP-based geolocation, this module requires the GeoLite2 Country and City -datasets, in binary format (CSV will not work!). The datasets may be -downloaded from MaxMind at https://dev.maxmind.com/geoip/geoip2/geolite2/. -Grab GeoLite2-Country.mmdb.gz and GeoLite2-City.mmdb.gz, and unzip them in the -directory corresponding to settings.GEOIP_PATH. -""" -__all__ = ["HAS_GEOIP2"] - -try: - import geoip2 # NOQA -except ImportError: - HAS_GEOIP2 = False -else: - from .base import GeoIP2, GeoIP2Exception - - HAS_GEOIP2 = True - __all__ += ["GeoIP2", "GeoIP2Exception"] diff --git a/django/contrib/gis/geoip2/resources.py b/django/contrib/gis/geoip2/resources.py deleted file mode 100644 index 74f422869747..000000000000 --- a/django/contrib/gis/geoip2/resources.py +++ /dev/null @@ -1,22 +0,0 @@ -def City(response): - return { - "city": response.city.name, - "continent_code": response.continent.code, - "continent_name": response.continent.name, - "country_code": response.country.iso_code, - "country_name": response.country.name, - "dma_code": response.location.metro_code, - "is_in_european_union": response.country.is_in_european_union, - "latitude": response.location.latitude, - "longitude": response.location.longitude, - "postal_code": response.postal.code, - "region": response.subdivisions[0].iso_code if response.subdivisions else None, - "time_zone": response.location.time_zone, - } - - -def Country(response): - return { - "country_code": response.country.iso_code, - "country_name": response.country.name, - } diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 0067c8fbf575..a1b00c364a33 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -66,6 +66,8 @@ details on these changes. * The ``django.contrib.gis.geoip2.GeoIP2.coords()`` method will be removed. +* The ``django.contrib.gis.geoip2.GeoIP2.open()`` method will be removed. + .. _deprecation-removed-in-5.1: 5.1 diff --git a/docs/ref/contrib/gis/geoip2.txt b/docs/ref/contrib/gis/geoip2.txt index dc735ff13ee0..aca31bf78b00 100644 --- a/docs/ref/contrib/gis/geoip2.txt +++ b/docs/ref/contrib/gis/geoip2.txt @@ -100,6 +100,10 @@ Instantiating This classmethod instantiates the GeoIP object from the given database path and given cache setting. +.. deprecated:: 5.1 + + Use the :class:`GeoIP2()` constructor instead. + Querying -------- diff --git a/docs/releases/4.2.9.txt b/docs/releases/4.2.9.txt new file mode 100644 index 000000000000..f98803f5eafb --- /dev/null +++ b/docs/releases/4.2.9.txt @@ -0,0 +1,12 @@ +========================== +Django 4.2.9 release notes +========================== + +*Expected January 2, 2024* + +Django 4.2.9 fixes a bug in 4.2.8. + +Bugfixes +======== + +* ... diff --git a/docs/releases/5.0.1.txt b/docs/releases/5.0.1.txt index 963dbff916d3..f1b6e0f8bbe1 100644 --- a/docs/releases/5.0.1.txt +++ b/docs/releases/5.0.1.txt @@ -2,7 +2,7 @@ Django 5.0.1 release notes ========================== -*Expected January 4, 2024* +*Expected January 2, 2024* Django 5.0.1 fixes several bugs in 5.0. diff --git a/docs/releases/5.1.txt b/docs/releases/5.1.txt index 33b4f1c491ca..395acb5a8902 100644 --- a/docs/releases/5.1.txt +++ b/docs/releases/5.1.txt @@ -310,6 +310,9 @@ Miscellaneous * The ``django.contrib.gis.geoip2.GeoIP2.coords()`` method is deprecated. Use ``django.contrib.gis.geoip2.GeoIP2.lon_lat()`` instead. +* The ``django.contrib.gis.geoip2.GeoIP2.open()`` method is deprecated. Use the + :class:`~django.contrib.gis.geoip2.GeoIP2` constructor instead. + Features removed in 5.1 ======================= diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 9cc64630a8c8..3233dbf5014b 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -41,6 +41,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 4.2.9 4.2.8 4.2.7 4.2.6 diff --git a/tests/gis_tests/test_geoip2.py b/tests/gis_tests/test_geoip2.py index 53f2b20bbc2b..acdfde9ec720 100644 --- a/tests/gis_tests/test_geoip2.py +++ b/tests/gis_tests/test_geoip2.py @@ -29,14 +29,13 @@ def test01_init(self): g1 = GeoIP2() # Everything inferred from GeoIP path path = settings.GEOIP_PATH g2 = GeoIP2(path, 0) # Passing in data path explicitly. - g3 = GeoIP2.open(path, 0) # MaxMind Python API syntax. # path accepts str and pathlib.Path. if isinstance(path, str): - g4 = GeoIP2(pathlib.Path(path)) + g3 = GeoIP2(pathlib.Path(path)) else: - g4 = GeoIP2(str(path)) + g3 = GeoIP2(str(path)) - for g in (g1, g2, g3, g4): + for g in (g1, g2, g3): self.assertTrue(g._country) self.assertTrue(g._city) @@ -198,3 +197,10 @@ def test_coords_deprecation_warning(self): e1, e2 = g.coords(self.fqdn) self.assertIsInstance(e1, float) self.assertIsInstance(e2, float) + + def test_open_deprecation_warning(self): + msg = "GeoIP2.open() is deprecated. Use GeoIP2() instead." + with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + g = GeoIP2.open(settings.GEOIP_PATH, 0) + self.assertTrue(g._country) + self.assertTrue(g._city)