Pārlūkot izejas kodu

Fixed #35841 -- Restored support for DB-IP databases in GeoIP2.

Thanks Felix Farquharson for the report and Claude Paroz for the
review.

Regression in 40b5b1596f7505416bd30d5d7582b5a9004ea7d5.

Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
Nick Pope 5 mēneši atpakaļ
vecāks
revīzija
3fad712a91

+ 23 - 5
django/contrib/gis/geoip2.py

@@ -34,6 +34,18 @@ else:
     __all__ += ["GeoIP2", "GeoIP2Exception"]
 
 
+# These are the values stored in the `database_type` field of the metadata.
+# See https://maxmind.github.io/MaxMind-DB/#database_type for details.
+SUPPORTED_DATABASE_TYPES = {
+    "DBIP-City-Lite",
+    "DBIP-Country-Lite",
+    "GeoIP2-City",
+    "GeoIP2-Country",
+    "GeoLite2-City",
+    "GeoLite2-Country",
+}
+
+
 class GeoIP2Exception(Exception):
     pass
 
@@ -106,7 +118,7 @@ class GeoIP2:
             )
 
         database_type = self._metadata.database_type
-        if not database_type.endswith(("City", "Country")):
+        if database_type not in SUPPORTED_DATABASE_TYPES:
             raise GeoIP2Exception(f"Unable to handle database edition: {database_type}")
 
     def __del__(self):
@@ -123,6 +135,14 @@ class GeoIP2:
     def _metadata(self):
         return self._reader.metadata()
 
+    @cached_property
+    def is_city(self):
+        return "City" in self._metadata.database_type
+
+    @cached_property
+    def is_country(self):
+        return "Country" in self._metadata.database_type
+
     def _query(self, query, *, require_city=False):
         if not isinstance(query, (str, ipaddress.IPv4Address, ipaddress.IPv6Address)):
             raise TypeError(
@@ -130,9 +150,7 @@ class GeoIP2:
                 "IPv6Address, not type %s" % type(query).__name__,
             )
 
-        is_city = self._metadata.database_type.endswith("City")
-
-        if require_city and not is_city:
+        if require_city and not self.is_city:
             raise GeoIP2Exception(f"Invalid GeoIP city data file: {self._path}")
 
         try:
@@ -141,7 +159,7 @@ class GeoIP2:
             # GeoIP2 only takes IP addresses, so try to resolve a hostname.
             query = socket.gethostbyname(query)
 
-        function = self._reader.city if is_city else self._reader.country
+        function = self._reader.city if self.is_city else self._reader.country
         return function(query)
 
     def city(self, query):

+ 3 - 0
docs/releases/5.1.3.txt

@@ -14,3 +14,6 @@ Bugfixes
   :class:`~django.core.validators.DomainNameValidator` accepted any input value
   that contained a valid domain name, rather than only input values that were a
   valid domain name (:ticket:`35845`).
+
+* Fixed a regression in Django 5.1 that prevented the use of DB-IP databases
+  with :class:`~django.contrib.gis.geoip2.GeoIP2` (:ticket:`35841`).

+ 14 - 0
tests/gis_tests/data/geoip2/README.md

@@ -12,3 +12,17 @@ Updates can be found in [this repository][1].
 
 [0]: https://github.com/maxmind/MaxMind-DB/blob/main/LICENSE-MIT
 [1]: https://github.com/maxmind/MaxMind-DB/tree/main/test-data
+
+# DB-IP Lite Test Databases
+
+The following test databases are provided under [this license][2]:
+
+- `dbip-city-lite-test.mmdb`
+- `dbip-country-lite-test.mmdb`
+
+They have been modified to strip them down to a minimal dataset for testing.
+
+Updates can be found at [this download page][3] from DB-IP.
+
+[2]: https://creativecommons.org/licenses/by/4.0/
+[3]: https://db-ip.com/db/lite.php

BIN
tests/gis_tests/data/geoip2/dbip-city-lite-test.mmdb


BIN
tests/gis_tests/data/geoip2/dbip-country-lite-test.mmdb


+ 27 - 3
tests/gis_tests/test_geoip2.py

@@ -124,7 +124,8 @@ class GeoLite2Test(SimpleTestCase):
 
     def test_country(self):
         g = GeoIP2(city="<invalid>")
-        self.assertIs(g._metadata.database_type.endswith("Country"), True)
+        self.assertIs(g.is_city, False)
+        self.assertIs(g.is_country, True)
         for query in self.query_values:
             with self.subTest(query=query):
                 self.assertEqual(g.country(query), self.expected_country)
@@ -137,7 +138,8 @@ class GeoLite2Test(SimpleTestCase):
 
     def test_country_using_city_database(self):
         g = GeoIP2(country="<invalid>")
-        self.assertIs(g._metadata.database_type.endswith("City"), True)
+        self.assertIs(g.is_city, True)
+        self.assertIs(g.is_country, False)
         for query in self.query_values:
             with self.subTest(query=query):
                 self.assertEqual(g.country(query), self.expected_country)
@@ -150,7 +152,8 @@ class GeoLite2Test(SimpleTestCase):
 
     def test_city(self):
         g = GeoIP2(country="<invalid>")
-        self.assertIs(g._metadata.database_type.endswith("City"), True)
+        self.assertIs(g.is_city, True)
+        self.assertIs(g.is_country, False)
         for query in self.query_values:
             with self.subTest(query=query):
                 self.assertEqual(g.city(query), self.expected_city)
@@ -224,6 +227,27 @@ class GeoIP2Test(GeoLite2Test):
     """Non-free GeoIP2 databases are supported."""
 
 
+@skipUnless(HAS_GEOIP2, "GeoIP2 is required.")
+@override_settings(
+    GEOIP_CITY="dbip-city-lite-test.mmdb",
+    GEOIP_COUNTRY="dbip-country-lite-test.mmdb",
+)
+class DBIPLiteTest(GeoLite2Test):
+    """DB-IP Lite databases are supported."""
+
+    expected_city = GeoLite2Test.expected_city | {
+        "accuracy_radius": None,
+        "city": "London (Shadwell)",
+        "latitude": 51.5181,
+        "longitude": -0.0714189,
+        "postal_code": None,
+        "region_code": None,
+        "time_zone": None,
+        # Kept for backward compatibility.
+        "region": None,
+    }
+
+
 @skipUnless(HAS_GEOIP2, "GeoIP2 is required.")
 class ErrorTest(SimpleTestCase):
     def test_missing_path(self):