Replaced no_spatialite by connection features
Refs #22632. Thanks Tim Graham for the review.
This commit is contained in:
parent
46c7707e50
commit
a7d964ab87
|
@ -11,8 +11,56 @@ from django.utils.encoding import python_2_unicode_compatible
|
|||
|
||||
class BaseSpatialFeatures(object):
|
||||
gis_enabled = True
|
||||
|
||||
# Does the database contain a SpatialRefSys model to store SRID information?
|
||||
has_spatialrefsys_table = True
|
||||
|
||||
# Can the `distance` GeoQuerySet method be applied on geodetic coordinate systems?
|
||||
supports_distance_geodetic = True
|
||||
# Does the database supports `left` and `right` lookups?
|
||||
supports_left_right_lookups = False
|
||||
# Is the database able to count vertices on polygons (with `num_points`)?
|
||||
supports_num_points_poly = True
|
||||
|
||||
# The following properties indicate if the database GIS extensions support
|
||||
# certain methods (dwithin, force_rhr, geohash, ...)
|
||||
@property
|
||||
def has_dwithin_lookup(self):
|
||||
return 'dwithin' in self.connection.ops.distance_functions
|
||||
|
||||
@property
|
||||
def has_force_rhr_method(self):
|
||||
return bool(self.connection.ops.force_rhr)
|
||||
|
||||
@property
|
||||
def has_geohash_method(self):
|
||||
return bool(self.connection.ops.geohash)
|
||||
|
||||
@property
|
||||
def has_make_line_method(self):
|
||||
return bool(self.connection.ops.make_line)
|
||||
|
||||
@property
|
||||
def has_perimeter_method(self):
|
||||
return bool(self.connection.ops.perimeter)
|
||||
|
||||
@property
|
||||
def has_reverse_method(self):
|
||||
return bool(self.connection.ops.reverse)
|
||||
|
||||
@property
|
||||
def has_snap_to_grid_method(self):
|
||||
return bool(self.connection.ops.snap_to_grid)
|
||||
|
||||
# Specifies whether the Collect and Extent aggregates are supported by the database
|
||||
@property
|
||||
def supports_collect_aggr(self):
|
||||
return 'Collect' in self.connection.ops.valid_aggregates
|
||||
|
||||
@property
|
||||
def supports_extent_aggr(self):
|
||||
return 'Extent' in self.connection.ops.valid_aggregates
|
||||
|
||||
|
||||
class BaseSpatialOperations(object):
|
||||
"""
|
||||
|
|
|
@ -10,6 +10,7 @@ from django.contrib.gis.db.backends.mysql.operations import MySQLOperations
|
|||
|
||||
class DatabaseFeatures(BaseSpatialFeatures, MySQLDatabaseFeatures):
|
||||
has_spatialrefsys_table = False
|
||||
supports_num_points_poly = False
|
||||
|
||||
|
||||
class DatabaseWrapper(MySQLDatabaseWrapper):
|
||||
|
|
|
@ -11,7 +11,7 @@ from django.contrib.gis.db.backends.postgis.schema import PostGISSchemaEditor
|
|||
|
||||
|
||||
class DatabaseFeatures(BaseSpatialFeatures, Psycopg2DatabaseFeatures):
|
||||
pass
|
||||
supports_left_right_lookups = True
|
||||
|
||||
|
||||
class DatabaseWrapper(Psycopg2DatabaseWrapper):
|
||||
|
|
|
@ -16,7 +16,9 @@ from django.utils import six
|
|||
|
||||
|
||||
class DatabaseFeatures(BaseSpatialFeatures, SQLiteDatabaseFeatures):
|
||||
pass
|
||||
supports_distance_geodetic = False
|
||||
# SpatiaLite can only count vertices in LineStrings
|
||||
supports_num_points_poly = False
|
||||
|
||||
|
||||
class DatabaseWrapper(SQLiteDatabaseWrapper):
|
||||
|
|
|
@ -65,17 +65,25 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
|||
name = 'spatialite'
|
||||
spatialite = True
|
||||
version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
|
||||
valid_aggregates = {'Extent', 'Union'}
|
||||
|
||||
@property
|
||||
def valid_aggregates(self):
|
||||
if self.spatial_version >= 3:
|
||||
return {'Collect', 'Extent', 'Union'}
|
||||
else:
|
||||
return {'Union'}
|
||||
|
||||
Adapter = SpatiaLiteAdapter
|
||||
Adaptor = Adapter # Backwards-compatibility alias.
|
||||
|
||||
area = 'Area'
|
||||
centroid = 'Centroid'
|
||||
collect = 'Collect'
|
||||
contained = 'MbrWithin'
|
||||
difference = 'Difference'
|
||||
distance = 'Distance'
|
||||
envelope = 'Envelope'
|
||||
extent = 'Extent'
|
||||
intersection = 'Intersection'
|
||||
length = 'GLength' # OpenGis defines Length, but this conflicts with an SQLite reserved keyword
|
||||
num_geom = 'NumGeometries'
|
||||
|
@ -180,6 +188,15 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
|||
agg_name = aggregate.__class__.__name__
|
||||
return agg_name in self.valid_aggregates
|
||||
|
||||
def convert_extent(self, box):
|
||||
"""
|
||||
Convert the polygon data received from Spatialite to min/max values.
|
||||
"""
|
||||
shell = Geometry(box).shell
|
||||
xmin, ymin = shell[0][:2]
|
||||
xmax, ymax = shell[2][:2]
|
||||
return (xmin, ymin, xmax, ymax)
|
||||
|
||||
def convert_geom(self, wkt, geo_field):
|
||||
"""
|
||||
Converts geometry WKT returned from a SpatiaLite aggregate.
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.db.models import Q
|
|||
from django.contrib.gis.geos import HAS_GEOS
|
||||
from django.contrib.gis.measure import D # alias for Distance
|
||||
from django.contrib.gis.tests.utils import (
|
||||
mysql, oracle, postgis, spatialite, no_oracle, no_spatialite
|
||||
mysql, oracle, postgis, spatialite, no_oracle
|
||||
)
|
||||
from django.test import TestCase, skipUnlessDBFeature
|
||||
|
||||
|
@ -50,7 +50,7 @@ class DistanceTest(TestCase):
|
|||
self.assertEqual(1, Interstate.objects.count())
|
||||
self.assertEqual(1, SouthTexasInterstate.objects.count())
|
||||
|
||||
@no_spatialite
|
||||
@skipUnlessDBFeature("has_dwithin_lookup")
|
||||
def test_dwithin(self):
|
||||
"""
|
||||
Test the `dwithin` lookup type.
|
||||
|
@ -139,7 +139,7 @@ class DistanceTest(TestCase):
|
|||
self.assertAlmostEqual(m_distances[i], c.distance.m, tol)
|
||||
self.assertAlmostEqual(ft_distances[i], c.distance.survey_ft, tol)
|
||||
|
||||
@no_spatialite
|
||||
@skipUnlessDBFeature("supports_distance_geodetic")
|
||||
def test_distance_geodetic(self):
|
||||
"""
|
||||
Test the `distance` GeoQuerySet method on geodetic coordinate systems.
|
||||
|
@ -354,7 +354,7 @@ class DistanceTest(TestCase):
|
|||
i10 = SouthTexasInterstate.objects.length().get(name='I-10')
|
||||
self.assertAlmostEqual(len_m2, i10.length.m, 2)
|
||||
|
||||
@no_spatialite
|
||||
@skipUnlessDBFeature("has_perimeter_method")
|
||||
def test_perimeter(self):
|
||||
"""
|
||||
Test the `perimeter` GeoQuerySet method.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from django.contrib.gis.db import models
|
||||
from django.contrib.gis.tests.utils import mysql, spatialite
|
||||
from django.contrib.gis.tests.utils import mysql
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
|
||||
# MySQL spatial indices can't handle NULL geometries.
|
||||
|
@ -58,15 +58,14 @@ class Truth(models.Model):
|
|||
app_label = 'geoapp'
|
||||
|
||||
|
||||
if not spatialite:
|
||||
class Feature(NamedModel):
|
||||
geom = models.GeometryField()
|
||||
|
||||
class Feature(NamedModel):
|
||||
geom = models.GeometryField()
|
||||
|
||||
class MinusOneSRID(models.Model):
|
||||
geom = models.PointField(srid=-1) # Minus one SRID.
|
||||
class MinusOneSRID(models.Model):
|
||||
geom = models.PointField(srid=-1) # Minus one SRID.
|
||||
|
||||
objects = models.GeoManager()
|
||||
objects = models.GeoManager()
|
||||
|
||||
class Meta:
|
||||
app_label = 'geoapp'
|
||||
class Meta:
|
||||
app_label = 'geoapp'
|
||||
|
|
|
@ -4,7 +4,6 @@ from __future__ import unicode_literals
|
|||
from datetime import datetime
|
||||
|
||||
from django.contrib.gis.geos import HAS_GEOS
|
||||
from django.contrib.gis.tests.utils import no_mysql, no_spatialite
|
||||
from django.contrib.gis.shortcuts import render_to_kmz
|
||||
from django.db.models import Count, Min
|
||||
from django.test import TestCase, skipUnlessDBFeature
|
||||
|
@ -39,8 +38,7 @@ class GeoRegressionTests(TestCase):
|
|||
}]
|
||||
render_to_kmz('gis/kml/placemarks.kml', {'places': places})
|
||||
|
||||
@no_spatialite
|
||||
@no_mysql
|
||||
@skipUnlessDBFeature("supports_extent_aggr")
|
||||
def test_extent(self):
|
||||
"Testing `extent` on a table with a single point. See #11827."
|
||||
pnt = City.objects.get(name='Pueblo').point
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.db import connection
|
|||
from django.contrib.gis import gdal
|
||||
from django.contrib.gis.geos import HAS_GEOS
|
||||
from django.contrib.gis.tests.utils import (
|
||||
no_mysql, no_oracle, no_spatialite, mysql, oracle, postgis, spatialite)
|
||||
no_mysql, no_oracle, mysql, oracle, postgis, spatialite)
|
||||
from django.test import TestCase, skipUnlessDBFeature
|
||||
from django.utils import six
|
||||
|
||||
|
@ -17,8 +17,6 @@ if HAS_GEOS:
|
|||
Point, LineString, LinearRing, Polygon, GeometryCollection)
|
||||
|
||||
from .models import Country, City, PennsylvaniaCity, State, Track
|
||||
|
||||
if HAS_GEOS and not spatialite:
|
||||
from .models import Feature, MinusOneSRID
|
||||
|
||||
|
||||
|
@ -156,7 +154,6 @@ class GeoModelTest(TestCase):
|
|||
c = City()
|
||||
self.assertEqual(c.point, None)
|
||||
|
||||
@no_spatialite # SpatiaLite does not support abstract geometry columns
|
||||
def test_geometryfield(self):
|
||||
"Testing the general GeometryField."
|
||||
Feature(name='Point', geom=Point(1, 1)).save()
|
||||
|
@ -266,9 +263,7 @@ class GeoLookupTest(TestCase):
|
|||
self.assertEqual('Texas', qs[0].name)
|
||||
|
||||
# Only PostGIS has `left` and `right` lookup types.
|
||||
@no_mysql
|
||||
@no_oracle
|
||||
@no_spatialite
|
||||
@skipUnlessDBFeature("supports_left_right_lookups")
|
||||
def test_left_right_lookups(self):
|
||||
"Testing the 'left' and 'right' lookup types."
|
||||
# Left: A << B => true if xmax(A) < xmin(B)
|
||||
|
@ -451,8 +446,7 @@ class GeoQuerySetTest(TestCase):
|
|||
for country in countries:
|
||||
self.assertIsInstance(country.envelope, Polygon)
|
||||
|
||||
@no_mysql
|
||||
@no_spatialite # SpatiaLite does not have an Extent function
|
||||
@skipUnlessDBFeature("supports_extent_aggr")
|
||||
def test_extent(self):
|
||||
"Testing the `extent` GeoQuerySet method."
|
||||
# Reference query:
|
||||
|
@ -466,9 +460,7 @@ class GeoQuerySetTest(TestCase):
|
|||
for val, exp in zip(extent, expected):
|
||||
self.assertAlmostEqual(exp, val, 4)
|
||||
|
||||
@no_mysql
|
||||
@no_oracle
|
||||
@no_spatialite
|
||||
@skipUnlessDBFeature("has_force_rhr_method")
|
||||
def test_force_rhr(self):
|
||||
"Testing GeoQuerySet.force_rhr()."
|
||||
rings = (
|
||||
|
@ -483,13 +475,9 @@ class GeoQuerySetTest(TestCase):
|
|||
s = State.objects.force_rhr().get(name='Foo')
|
||||
self.assertEqual(rhr_rings, s.force_rhr.coords)
|
||||
|
||||
@no_mysql
|
||||
@no_oracle
|
||||
@no_spatialite
|
||||
@skipUnlessDBFeature("has_geohash_method")
|
||||
def test_geohash(self):
|
||||
"Testing GeoQuerySet.geohash()."
|
||||
if not connection.ops.geohash:
|
||||
return
|
||||
# Reference query:
|
||||
# SELECT ST_GeoHash(point) FROM geoapp_city WHERE name='Houston';
|
||||
# SELECT ST_GeoHash(point, 5) FROM geoapp_city WHERE name='Houston';
|
||||
|
@ -501,7 +489,7 @@ class GeoQuerySetTest(TestCase):
|
|||
|
||||
def test_geojson(self):
|
||||
"Testing GeoJSON output from the database using GeoQuerySet.geojson()."
|
||||
# Only PostGIS 1.3.4+ and SpatiaLite 3.0+ support GeoJSON.
|
||||
# Only PostGIS and SpatiaLite 3.0+ support GeoJSON.
|
||||
if not connection.ops.geojson:
|
||||
self.assertRaises(NotImplementedError, Country.objects.all().geojson, field_name='mpoly')
|
||||
return
|
||||
|
@ -520,17 +508,15 @@ class GeoQuerySetTest(TestCase):
|
|||
# SELECT ST_AsGeoJson("geoapp_city"."point", 8, 0) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Pueblo';
|
||||
self.assertEqual(pueblo_json, City.objects.geojson().get(name='Pueblo').geojson)
|
||||
|
||||
# 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston';
|
||||
# 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston';
|
||||
# SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston';
|
||||
# This time we want to include the CRS by using the `crs` keyword.
|
||||
self.assertEqual(houston_json, City.objects.geojson(crs=True, model_att='json').get(name='Houston').json)
|
||||
|
||||
# 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Victoria';
|
||||
# 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston';
|
||||
# SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston';
|
||||
# This time we include the bounding box by using the `bbox` keyword.
|
||||
self.assertEqual(victoria_json, City.objects.geojson(bbox=True).get(name='Victoria').geojson)
|
||||
|
||||
# 1.(3|4).x: SELECT ST_AsGeoJson("geoapp_city"."point", 5, 3) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Chicago';
|
||||
# SELECT ST_AsGeoJson("geoapp_city"."point", 5, 3) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Chicago';
|
||||
# Finally, we set every available keyword.
|
||||
self.assertEqual(chicago_json, City.objects.geojson(bbox=True, crs=True, precision=5).get(name='Chicago').geojson)
|
||||
|
||||
|
@ -581,9 +567,7 @@ class GeoQuerySetTest(TestCase):
|
|||
self.assertEqual('<Point><coordinates>-104.609252,38.255001</coordinates></Point>', ptown.kml)
|
||||
|
||||
# Only PostGIS has support for the MakeLine aggregate.
|
||||
@no_mysql
|
||||
@no_oracle
|
||||
@no_spatialite
|
||||
@skipUnlessDBFeature("has_make_line_method")
|
||||
def test_make_line(self):
|
||||
"Testing the `make_line` GeoQuerySet method."
|
||||
# Ensuring that a `TypeError` is raised on models without PointFields.
|
||||
|
@ -610,8 +594,7 @@ class GeoQuerySetTest(TestCase):
|
|||
else:
|
||||
self.assertEqual(1, c.num_geom)
|
||||
|
||||
@no_mysql
|
||||
@no_spatialite # SpatiaLite can only count vertices in LineStrings
|
||||
@skipUnlessDBFeature("supports_num_points_poly")
|
||||
def test_num_points(self):
|
||||
"Testing the `num_points` GeoQuerySet method."
|
||||
for c in Country.objects.num_points():
|
||||
|
@ -647,8 +630,7 @@ class GeoQuerySetTest(TestCase):
|
|||
tol = 0.000000001
|
||||
self.assertEqual(True, ref[c.name].equals_exact(c.point_on_surface, tol))
|
||||
|
||||
@no_mysql
|
||||
@no_spatialite
|
||||
@skipUnlessDBFeature("has_reverse_method")
|
||||
def test_reverse_geom(self):
|
||||
"Testing GeoQuerySet.reverse_geom()."
|
||||
coords = [(-95.363151, 29.763374), (-95.448601, 29.713803)]
|
||||
|
@ -673,9 +655,7 @@ class GeoQuerySetTest(TestCase):
|
|||
self.assertAlmostEqual(c1[0] * xfac, c2[0], tol)
|
||||
self.assertAlmostEqual(c1[1] * yfac, c2[1], tol)
|
||||
|
||||
@no_mysql
|
||||
@no_oracle
|
||||
@no_spatialite
|
||||
@skipUnlessDBFeature("has_snap_to_grid_method")
|
||||
def test_snap_to_grid(self):
|
||||
"Testing GeoQuerySet.snap_to_grid()."
|
||||
# Let's try and break snap_to_grid() with bad combinations of arguments.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.gis.geos import HAS_GEOS
|
||||
from django.contrib.gis.tests.utils import mysql, no_mysql, no_oracle, no_spatialite
|
||||
from django.contrib.gis.tests.utils import mysql, no_mysql, no_oracle
|
||||
from django.test import TestCase, skipUnlessDBFeature
|
||||
|
||||
if HAS_GEOS:
|
||||
|
@ -60,8 +60,7 @@ class RelatedGeoModelTest(TestCase):
|
|||
qs = list(City.objects.filter(name=name).transform(srid, field_name='location__point'))
|
||||
check_pnt(GEOSGeometry(wkt, srid), qs[0].location.point)
|
||||
|
||||
@no_mysql
|
||||
@no_spatialite
|
||||
@skipUnlessDBFeature("supports_extent_aggr")
|
||||
def test04a_related_extent_aggregate(self):
|
||||
"Testing the `extent` GeoQuerySet aggregates on related geographic models."
|
||||
# This combines the Extent and Union aggregates into one query
|
||||
|
@ -265,9 +264,7 @@ class RelatedGeoModelTest(TestCase):
|
|||
# Should be `None`, and not a 'dummy' model.
|
||||
self.assertEqual(None, b.author)
|
||||
|
||||
@no_mysql
|
||||
@no_oracle
|
||||
@no_spatialite
|
||||
@skipUnlessDBFeature("supports_collect_aggr")
|
||||
def test14_collect(self):
|
||||
"Testing the `collect` GeoQuerySet method and `Collect` aggregate."
|
||||
# Reference query:
|
||||
|
|
|
@ -29,9 +29,6 @@ def no_mysql(func):
|
|||
return no_backend(func, 'mysql')
|
||||
|
||||
|
||||
def no_spatialite(func):
|
||||
return no_backend(func, 'spatialite')
|
||||
|
||||
# Shortcut booleans to omit only portions of tests.
|
||||
_default_db = settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'].rsplit('.')[-1]
|
||||
oracle = _default_db == 'oracle'
|
||||
|
|
|
@ -270,11 +270,11 @@ Method PostGIS Oracle SpatiaLite
|
|||
==================================== ======= ====== ==========
|
||||
:meth:`GeoQuerySet.area` X X X
|
||||
:meth:`GeoQuerySet.centroid` X X X
|
||||
:meth:`GeoQuerySet.collect` X
|
||||
:meth:`GeoQuerySet.collect` X (from v3.0)
|
||||
:meth:`GeoQuerySet.difference` X X X
|
||||
:meth:`GeoQuerySet.distance` X X X
|
||||
:meth:`GeoQuerySet.envelope` X X
|
||||
:meth:`GeoQuerySet.extent` X X
|
||||
:meth:`GeoQuerySet.extent` X X (from v3.0)
|
||||
:meth:`GeoQuerySet.extent3d` X
|
||||
:meth:`GeoQuerySet.force_rhr` X
|
||||
:meth:`GeoQuerySet.geohash` X
|
||||
|
|
|
@ -83,6 +83,9 @@ Minor features
|
|||
* Compatibility shims for ``SpatialRefSys`` and ``GeometryColumns`` changed in
|
||||
Django 1.2 have been removed.
|
||||
|
||||
* The Spatialite backend now supports ``Collect`` and ``Extent`` aggregates
|
||||
when the database version is 3.0 or later.
|
||||
|
||||
:mod:`django.contrib.messages`
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
Loading…
Reference in New Issue