Removed redundant database vendor helpers in gis_tests/utils.py.

This commit is contained in:
Tim Graham 2020-11-14 09:08:30 -05:00 committed by Mariusz Felisiak
parent 9f91122ed8
commit e3ece0144a
12 changed files with 71 additions and 88 deletions

View File

@ -12,6 +12,7 @@ class BaseSpatialOperations:
# an attribute for the spatial database version tuple (if applicable)
postgis = False
spatialite = False
mariadb = False
mysql = False
oracle = False
spatial_version = None

View File

@ -12,13 +12,19 @@ from django.utils.functional import cached_property
class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
mysql = True
name = 'mysql'
geom_func_prefix = 'ST_'
Adapter = WKTAdapter
@cached_property
def mariadb(self):
return self.connection.mysql_is_mariadb
@cached_property
def mysql(self):
return not self.connection.mysql_is_mariadb
@cached_property
def select(self):
return self.geom_func_prefix + 'AsBinary(%s)'

View File

@ -1,5 +1,3 @@
import unittest
from django.contrib.gis.db.models.functions import (
Area, Distance, Length, Perimeter, Transform, Union,
)
@ -9,7 +7,7 @@ from django.db import NotSupportedError, connection
from django.db.models import Exists, F, OuterRef, Q
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
from ..utils import FuncTestMixin, mysql, oracle, postgis, spatialite
from ..utils import FuncTestMixin
from .models import (
AustraliaCity, CensusZipcode, Interstate, SouthTexasCity, SouthTexasCityFt,
SouthTexasInterstate, SouthTexasZipcode,
@ -79,9 +77,9 @@ class DistanceTest(TestCase):
# Now performing the `dwithin` queries on a geodetic coordinate system.
for dist in au_dists:
with self.subTest(dist=dist):
type_error = isinstance(dist, D) and not oracle
type_error = isinstance(dist, D) and not connection.ops.oracle
if isinstance(dist, tuple):
if oracle or spatialite:
if connection.ops.oracle or connection.ops.spatialite:
# Result in meters
dist = dist[1]
else:
@ -137,7 +135,7 @@ class DistanceTest(TestCase):
'Melbourne', 'Mittagong', 'Shellharbour',
'Sydney', 'Thirroul', 'Wollongong',
]
if spatialite:
if connection.ops.spatialite:
# SpatiaLite is less accurate and returns 102.8km for Batemans Bay.
expected_cities.pop(0)
self.assertEqual(expected_cities, self.get_names(dist_qs))
@ -216,8 +214,9 @@ class DistanceTest(TestCase):
SouthTexasCity.objects.count(),
)
@unittest.skipUnless(mysql, 'This is a MySQL-specific test')
def test_mysql_geodetic_distance_error(self):
if not connection.ops.mysql:
self.skipTest('This is a MySQL-specific test.')
msg = 'Only numeric values of degree units are allowed on geodetic distance queries.'
with self.assertRaisesMessage(ValueError, msg):
AustraliaCity.objects.filter(point__distance_lte=(Point(0, 0), D(m=100))).exists()
@ -313,7 +312,7 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
"""
lagrange = GEOSGeometry('POINT(805066.295722839 4231496.29461335)', 32140)
houston = SouthTexasCity.objects.annotate(dist=Distance('point', lagrange)).order_by('id').first()
tol = 2 if oracle else 5
tol = 2 if connection.ops.oracle else 5
self.assertAlmostEqual(
houston.dist.m,
147075.069813,
@ -348,7 +347,7 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
# Original query done on PostGIS, have to adjust AlmostEqual tolerance
# for Oracle.
tol = 2 if oracle else 5
tol = 2 if connection.ops.oracle else 5
# Ensuring expected distances are returned for each distance queryset.
for qs in dist_qs:
@ -376,12 +375,12 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
for city, distance in zip(qs, distances):
with self.subTest(city=city, distance=distance):
# Testing equivalence to within a meter (kilometer on SpatiaLite).
tol = -3 if spatialite else 0
tol = -3 if connection.ops.spatialite else 0
self.assertAlmostEqual(distance, city.distance.m, tol)
@skipUnlessDBFeature("has_Distance_function", "supports_distance_geodetic")
def test_distance_geodetic_spheroid(self):
tol = 2 if oracle else 4
tol = 2 if connection.ops.oracle else 4
# Got the reference distances using the raw SQL statements:
# SELECT ST_distance_spheroid(point, ST_GeomFromText('POINT(151.231341 -33.952685)', 4326),
@ -408,7 +407,7 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
for i, c in enumerate(qs):
with self.subTest(c=c):
self.assertAlmostEqual(spheroid_distances[i], c.distance.m, tol)
if postgis or spatialite:
if connection.ops.postgis or connection.ops.spatialite:
# PostGIS uses sphere-only distances by default, testing these as well.
qs = AustraliaCity.objects.exclude(id=hillsdale.id).annotate(
distance=Distance('point', hillsdale.point)
@ -521,7 +520,7 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
if connection.features.supports_length_geodetic:
qs = Interstate.objects.annotate(length=Length('path'))
tol = 2 if oracle else 3
tol = 2 if connection.ops.oracle else 3
self.assertAlmostEqual(len_m1, qs[0].length.m, tol)
# TODO: test with spheroid argument (True and False)
else:
@ -547,7 +546,7 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
# Reference query:
# SELECT ST_Perimeter(distapp_southtexaszipcode.poly) FROM distapp_southtexaszipcode;
perim_m = [18404.3550889361, 15627.2108551001, 20632.5588368978, 17094.5996143697]
tol = 2 if oracle else 7
tol = 2 if connection.ops.oracle else 7
qs = SouthTexasZipcode.objects.annotate(perimeter=Perimeter('poly')).order_by('name')
for i, z in enumerate(qs):
self.assertAlmostEqual(perim_m[i], z.perimeter.m, tol)

View File

@ -1,12 +1,9 @@
from unittest import skipUnless
from django.contrib.gis.db.models import F, GeometryField, Value, functions
from django.contrib.gis.geos import Point, Polygon
from django.db import connection
from django.db.models import Count, Min
from django.test import TestCase, skipUnlessDBFeature
from ..utils import postgis
from .models import City, ManyPointModel, MultiFields
@ -25,7 +22,7 @@ class GeoExpressionsTests(TestCase):
self.assertTrue(point.equals_exact(p.transform(4326, clone=True), 10 ** -5))
self.assertEqual(point.srid, 4326)
@skipUnless(postgis, 'Only postgis has geography fields.')
@skipUnlessDBFeature('supports_geography')
def test_geography_value(self):
p = Polygon(((1, 1), (1, 2), (2, 2), (2, 1), (1, 1)))
area = City.objects.annotate(a=functions.Area(Value(p, GeometryField(srid=4326, geography=True)))).first().a

View File

@ -12,7 +12,7 @@ from django.db import NotSupportedError, connection
from django.db.models import IntegerField, Sum, Value
from django.test import TestCase, skipUnlessDBFeature
from ..utils import FuncTestMixin, mariadb, mysql, oracle, postgis
from ..utils import FuncTestMixin
from .models import City, Country, CountryWebMercator, State, Track
@ -87,7 +87,7 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
# WHERE "geoapp_city"."name" = 'Chicago';
# Finally, we set every available keyword.
# MariaDB doesn't limit the number of decimals in bbox.
if mariadb:
if connection.ops.mariadb:
chicago_json['bbox'] = [-87.650175, 41.850385, -87.650175, 41.850385]
self.assertJSONEqual(
City.objects.annotate(
@ -105,7 +105,7 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
qs.annotate(gml=functions.AsGML('name'))
ptown = City.objects.annotate(gml=functions.AsGML('point', precision=9)).get(name='Pueblo')
if oracle:
if connection.ops.oracle:
# No precision parameter for Oracle :-/
gml_regex = re.compile(
r'^<gml:Point srsName="EPSG:4326" xmlns:gml="http://www.opengis.net/gml">'
@ -167,7 +167,7 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
wkt = City.objects.annotate(
wkt=functions.AsWKT(Point(1, 2, srid=4326)),
).first().wkt
self.assertEqual(wkt, 'POINT (1.0 2.0)' if oracle else 'POINT(1 2)')
self.assertEqual(wkt, 'POINT (1.0 2.0)' if connection.ops.oracle else 'POINT(1 2)')
@skipUnlessDBFeature("has_Azimuth_function")
def test_azimuth(self):
@ -184,11 +184,11 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
# num_seg is the number of segments per quarter circle.
return (4 * num_seg) + 1
expected_areas = (169, 136) if postgis else (171, 126)
expected_areas = (169, 136) if connection.ops.postgis else (171, 126)
qs = Country.objects.annotate(circle=functions.BoundingCircle('mpoly')).order_by('name')
self.assertAlmostEqual(qs[0].circle.area, expected_areas[0], 0)
self.assertAlmostEqual(qs[1].circle.area, expected_areas[1], 0)
if postgis:
if connection.ops.postgis:
# By default num_seg=48.
self.assertEqual(qs[0].circle.num_points, circle_num_points(48))
self.assertEqual(qs[1].circle.num_points, circle_num_points(48))
@ -199,7 +199,7 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
qs = Country.objects.annotate(
circle=functions.BoundingCircle('mpoly', num_seg=num_seq),
).order_by('name')
if postgis:
if connection.ops.postgis:
self.assertGreater(qs[0].circle.area, 168.4, 0)
self.assertLess(qs[0].circle.area, 169.5, 0)
self.assertAlmostEqual(qs[1].circle.area, 136, 0)
@ -212,7 +212,7 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
@skipUnlessDBFeature("has_Centroid_function")
def test_centroid(self):
qs = State.objects.exclude(poly__isnull=True).annotate(centroid=functions.Centroid('poly'))
tol = 1.8 if mysql else (0.1 if oracle else 0.00001)
tol = 1.8 if connection.ops.mysql else (0.1 if connection.ops.oracle else 0.00001)
for state in qs:
self.assertTrue(state.poly.centroid.equals_exact(state.centroid, tol))
@ -224,7 +224,7 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
geom = Point(5, 23, srid=4326)
qs = Country.objects.annotate(diff=functions.Difference('mpoly', geom))
# Oracle does something screwy with the Texas geometry.
if oracle:
if connection.ops.oracle:
qs = qs.exclude(name='Texas')
for c in qs:
@ -236,7 +236,7 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
geom = Point(556597.4, 2632018.6, srid=3857) # Spherical Mercator
qs = Country.objects.annotate(difference=functions.Difference('mpoly', geom))
# Oracle does something screwy with the Texas geometry.
if oracle:
if connection.ops.oracle:
qs = qs.exclude(name='Texas')
for c in qs:
self.assertTrue(c.mpoly.difference(geom).equals(c.difference))
@ -396,9 +396,9 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
qs = City.objects.filter(point__isnull=False).annotate(num_geom=functions.NumGeometries('point'))
for city in qs:
# Oracle and PostGIS return 1 for the number of geometries on
# non-collections, whereas MySQL returns None.
if mysql:
# The results for the number of geometries on non-collections
# depends on the database.
if connection.ops.mysql or connection.ops.mariadb:
self.assertIsNone(city.num_geom)
else:
self.assertEqual(1, city.num_geom)
@ -519,7 +519,7 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
geom = Point(5, 23, srid=4326)
qs = Country.objects.annotate(sym_difference=functions.SymDifference('mpoly', geom))
# Oracle does something screwy with the Texas geometry.
if oracle:
if connection.ops.oracle:
qs = qs.exclude(name='Texas')
for country in qs:
self.assertTrue(country.mpoly.sym_difference(geom).equals(country.sym_difference))
@ -562,7 +562,7 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
intersection=functions.Intersection('mpoly', geom),
)
if oracle:
if connection.ops.oracle:
# Should be able to execute the queries; however, they won't be the same
# as GEOS (because Oracle doesn't use GEOS internally like PostGIS or
# SpatiaLite).

View File

@ -1,10 +1,7 @@
from unittest import skipUnless
from django.db import connection
from django.db.models import Index
from django.test import TransactionTestCase
from ..utils import mysql, oracle, postgis
from .models import City
@ -22,17 +19,18 @@ class SchemaIndexesTests(TransactionTestCase):
}
def has_spatial_indexes(self, table):
if mysql:
if connection.ops.mysql:
with connection.cursor() as cursor:
return connection.introspection.supports_spatial_index(cursor, table)
elif oracle:
elif connection.ops.oracle:
# Spatial indexes in Meta.indexes are not supported by the Oracle
# backend (see #31252).
return False
return True
@skipUnless(postgis, 'This is a PostGIS-specific test.')
def test_using_sql(self):
if not connection.ops.postgis:
self.skipTest('This is a PostGIS-specific test.')
index = Index(fields=['point'])
editor = connection.schema_editor()
self.assertIn(

View File

@ -13,9 +13,7 @@ from django.db.models import F, OuterRef, Subquery
from django.test import TestCase, skipUnlessDBFeature
from django.test.utils import CaptureQueriesContext
from ..utils import (
mariadb, mysql, oracle, postgis, skipUnlessGISLookup, spatialite,
)
from ..utils import skipUnlessGISLookup
from .models import (
City, Country, Feature, MinusOneSRID, MultiFields, NonConcreteModel,
PennsylvaniaCity, State, Track,
@ -109,7 +107,7 @@ class GeoModelTest(TestCase):
# Constructing & querying with a point from a different SRID. Oracle
# `SDO_OVERLAPBDYINTERSECT` operates differently from
# `ST_Intersects`, so contains is used instead.
if oracle:
if connection.ops.oracle:
tx = Country.objects.get(mpoly__contains=other_srid_pnt)
else:
tx = Country.objects.get(mpoly__intersects=other_srid_pnt)
@ -299,7 +297,7 @@ class GeoLookupTest(TestCase):
invalid_geom = fromstr('POLYGON((0 0, 0 1, 1 1, 1 0, 1 1, 1 0, 0 0))')
State.objects.create(name='invalid', poly=invalid_geom)
qs = State.objects.all()
if oracle or (mysql and connection.mysql_version < (8, 0, 0)):
if connection.ops.oracle or (connection.ops.mysql and connection.mysql_version < (8, 0, 0)):
# Kansas has adjacent vertices with distance 6.99244813842e-12
# which is smaller than the default Oracle tolerance.
# It's invalid on MySQL < 8 also.
@ -453,12 +451,11 @@ class GeoLookupTest(TestCase):
with self.assertRaises(e):
qs.count()
# Relate works differently for the different backends.
if postgis or spatialite or mariadb:
contains_mask = 'T*T***FF*'
within_mask = 'T*F**F***'
intersects_mask = 'T********'
elif oracle:
# Relate works differently on Oracle.
if connection.ops.oracle:
contains_mask = 'contains'
within_mask = 'inside'
# TODO: This is not quite the same as the PostGIS mask above
@ -477,7 +474,7 @@ class GeoLookupTest(TestCase):
self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, within_mask)).name)
# Testing intersection relation mask.
if not oracle:
if not connection.ops.oracle:
if connection.features.supports_transform:
self.assertEqual(
Country.objects.get(mpoly__relate=(pnt1, intersects_mask)).name,
@ -487,7 +484,7 @@ class GeoLookupTest(TestCase):
self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, intersects_mask)).name)
# With a complex geometry expression
mask = 'anyinteract' if oracle else within_mask
mask = 'anyinteract' if connection.ops.oracle else within_mask
self.assertFalse(City.objects.exclude(point__relate=(functions.Union('point', 'point'), mask)))
def test_gis_lookups_with_complex_expressions(self):

View File

@ -2,7 +2,6 @@
Tests for geography support in PostGIS
"""
import os
from unittest import skipUnless
from django.contrib.gis.db import models
from django.contrib.gis.db.models.functions import Area, Distance
@ -11,7 +10,7 @@ from django.db import NotSupportedError, connection
from django.db.models.functions import Cast
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
from ..utils import FuncTestMixin, oracle, postgis, spatialite
from ..utils import FuncTestMixin
from .models import City, County, Zipcode
@ -37,9 +36,10 @@ class GeographyTest(TestCase):
for cities in [cities1, cities2]:
self.assertEqual(['Dallas', 'Houston', 'Oklahoma City'], cities)
@skipUnless(postgis, "This is a PostGIS-specific test")
def test04_invalid_operators_functions(self):
"Ensuring exceptions are raised for operators & functions invalid on geography fields."
if not connection.ops.postgis:
self.skipTest('This is a PostGIS-specific test.')
# Only a subset of the geometry functions & operator are available
# to PostGIS geography types. For more information, visit:
# http://postgis.refractions.net/documentation/manual-1.5/ch08.html#PostGIS_GeographyFunctions
@ -108,9 +108,9 @@ class GeographyFunctionTests(FuncTestMixin, TestCase):
"""
Testing Distance() support on non-point geography fields.
"""
if oracle:
if connection.ops.oracle:
ref_dists = [0, 4899.68, 8081.30, 9115.15]
elif spatialite:
elif connection.ops.spatialite:
# SpatiaLite returns non-zero distance for polygons and points
# covered by that polygon.
ref_dists = [326.61, 4899.68, 8081.30, 9115.15]
@ -124,13 +124,13 @@ class GeographyFunctionTests(FuncTestMixin, TestCase):
for z, ref in zip(qs, ref_dists):
self.assertAlmostEqual(z.distance.m, ref, 2)
if postgis:
if connection.ops.postgis:
# PostGIS casts geography to geometry when distance2 is calculated.
ref_dists = [0, 4899.68, 8081.30, 9115.15]
for z, ref in zip(qs, ref_dists):
self.assertAlmostEqual(z.distance2.m, ref, 2)
if not spatialite:
if not connection.ops.spatialite:
# Distance function combined with a lookup.
hzip = Zipcode.objects.get(code='77002')
self.assertEqual(qs.get(distance__lte=0), hzip)

View File

@ -10,8 +10,6 @@ from django.test import (
TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature,
)
from ..utils import mysql, oracle
try:
GeometryColumns = connection.ops.geometry_columns()
HAS_GEOMETRY_COLUMNS = True
@ -30,7 +28,7 @@ class OperationTestCase(TransactionTestCase):
@property
def has_spatial_indexes(self):
if mysql:
if connection.ops.mysql:
with connection.cursor() as cursor:
return connection.introspection.supports_spatial_index(cursor, 'gis_neighborhood')
return True
@ -120,7 +118,7 @@ class OperationTests(OperationTestCase):
def test_geom_col_name(self):
self.assertEqual(
GeometryColumns.geom_col_name(),
'column_name' if oracle else 'f_geometry_column',
'column_name' if connection.ops.oracle else 'f_geometry_column',
)
@skipUnlessDBFeature('supports_raster')

View File

@ -10,7 +10,6 @@ from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
from django.test.utils import modify_settings
from ..test_data import TEST_DATA
from ..utils import mariadb
from .models import AllOGRFields
@ -142,7 +141,7 @@ class OGRInspectTest(SimpleTestCase):
else:
self.assertIn(' f_decimal = models.DecimalField(max_digits=0, decimal_places=0)', model_def)
self.assertIn(' f_int = models.IntegerField()', model_def)
if not mariadb:
if not connection.ops.mariadb:
# Probably a bug between GDAL and MariaDB on time fields.
self.assertIn(' f_datetime = models.DateTimeField()', model_def)
self.assertIn(' f_time = models.TimeField()', model_def)

View File

@ -4,8 +4,6 @@ from django.db import connection
from django.test import TestCase, skipUnlessDBFeature
from django.utils.functional import cached_property
from .utils import oracle, postgis, spatialite
test_srs = ({
'srid': 4326,
'auth_name': ('EPSG', True),
@ -75,13 +73,15 @@ class SpatialRefSysTest(TestCase):
# also, Oracle Spatial seems to add extraneous info to fields, hence the
# the testing with the 'startswith' flag.
auth_name, oracle_flag = sd['auth_name']
if postgis or (oracle and oracle_flag):
self.assertTrue(srs.auth_name.startswith(auth_name))
# Compare case-insensitively because srs.auth_name is lowercase
# ("epsg") on Spatialite.
if not connection.ops.oracle or oracle_flag:
self.assertIs(srs.auth_name.upper().startswith(auth_name), True)
self.assertEqual(sd['auth_srid'], srs.auth_srid)
# No PROJ and different srtext on oracle backends :(
if postgis:
# No PROJ and different srtext on Oracle.
if not connection.ops.oracle:
self.assertTrue(srs.wkt.startswith(sd['srtext']))
self.assertRegex(srs.proj4text, sd['proj_re'])
@ -94,14 +94,9 @@ class SpatialRefSysTest(TestCase):
self.assertTrue(sr.spheroid.startswith(sd['spheroid']))
self.assertEqual(sd['geographic'], sr.geographic)
self.assertEqual(sd['projected'], sr.projected)
if not (spatialite and not sd['spatialite']):
# Can't get 'NAD83 / Texas South Central' from PROJ string
# on SpatiaLite
self.assertTrue(sr.name.startswith(sd['name']))
self.assertIs(sr.name.startswith(sd['name']), True)
# Testing the SpatialReference object directly.
if postgis or spatialite:
if not connection.ops.oracle:
srs = sr.srs
self.assertRegex(srs.proj, sd['proj_re'])
self.assertTrue(srs.wkt.startswith(sd['srtext']))

View File

@ -24,16 +24,9 @@ def skipUnlessGISLookup(*gis_lookups):
return decorator
# Shortcut booleans to omit only portions of tests.
_default_db = settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'].rsplit('.')[-1]
oracle = _default_db == 'oracle'
postgis = _default_db == 'postgis'
mysql = _default_db == 'mysql'
mariadb = mysql and connection.mysql_is_mariadb
spatialite = _default_db == 'spatialite'
# MySQL spatial indices can't handle NULL geometries.
gisfield_may_be_null = not mysql
gisfield_may_be_null = _default_db != 'mysql'
class FuncTestMixin: