summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaude Paroz <claude@2xlibre.net>2014-08-19 18:47:23 +0200
committerClaude Paroz <claude@2xlibre.net>2014-08-23 15:41:13 +0200
commita7d964ab87ad7352af3e33b8f3c12e4643a80f02 (patch)
tree7580438c83006bced53611c73b8cab75dc607cac
parent46c7707e50400e237cbd774a31ad801669ca3905 (diff)
Replaced no_spatialite by connection features
Refs #22632. Thanks Tim Graham for the review.
-rw-r--r--django/contrib/gis/db/backends/base.py48
-rw-r--r--django/contrib/gis/db/backends/mysql/base.py1
-rw-r--r--django/contrib/gis/db/backends/postgis/base.py2
-rw-r--r--django/contrib/gis/db/backends/spatialite/base.py4
-rw-r--r--django/contrib/gis/db/backends/spatialite/operations.py19
-rw-r--r--django/contrib/gis/tests/distapp/tests.py8
-rw-r--r--django/contrib/gis/tests/geoapp/models.py17
-rw-r--r--django/contrib/gis/tests/geoapp/test_regress.py4
-rw-r--r--django/contrib/gis/tests/geoapp/tests.py46
-rw-r--r--django/contrib/gis/tests/relatedapp/tests.py9
-rw-r--r--django/contrib/gis/tests/utils.py3
-rw-r--r--docs/ref/contrib/gis/db-api.txt4
-rw-r--r--docs/releases/1.8.txt3
13 files changed, 105 insertions, 63 deletions
diff --git a/django/contrib/gis/db/backends/base.py b/django/contrib/gis/db/backends/base.py
index e281ba073e..b7dbee4908 100644
--- a/django/contrib/gis/db/backends/base.py
+++ b/django/contrib/gis/db/backends/base.py
@@ -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):
"""
diff --git a/django/contrib/gis/db/backends/mysql/base.py b/django/contrib/gis/db/backends/mysql/base.py
index 077a8a1dc1..1a1221032b 100644
--- a/django/contrib/gis/db/backends/mysql/base.py
+++ b/django/contrib/gis/db/backends/mysql/base.py
@@ -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):
diff --git a/django/contrib/gis/db/backends/postgis/base.py b/django/contrib/gis/db/backends/postgis/base.py
index abeb4556eb..cb2cbe53aa 100644
--- a/django/contrib/gis/db/backends/postgis/base.py
+++ b/django/contrib/gis/db/backends/postgis/base.py
@@ -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):
diff --git a/django/contrib/gis/db/backends/spatialite/base.py b/django/contrib/gis/db/backends/spatialite/base.py
index 3bb949c4aa..f3c22bdb20 100644
--- a/django/contrib/gis/db/backends/spatialite/base.py
+++ b/django/contrib/gis/db/backends/spatialite/base.py
@@ -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):
diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py
index 7064912413..b0862251ee 100644
--- a/django/contrib/gis/db/backends/spatialite/operations.py
+++ b/django/contrib/gis/db/backends/spatialite/operations.py
@@ -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.
diff --git a/django/contrib/gis/tests/distapp/tests.py b/django/contrib/gis/tests/distapp/tests.py
index 19af28f4e9..348fe6f6cc 100644
--- a/django/contrib/gis/tests/distapp/tests.py
+++ b/django/contrib/gis/tests/distapp/tests.py
@@ -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.
diff --git a/django/contrib/gis/tests/geoapp/models.py b/django/contrib/gis/tests/geoapp/models.py
index 2550854b76..1e07e00d9d 100644
--- a/django/contrib/gis/tests/geoapp/models.py
+++ b/django/contrib/gis/tests/geoapp/models.py
@@ -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'
diff --git a/django/contrib/gis/tests/geoapp/test_regress.py b/django/contrib/gis/tests/geoapp/test_regress.py
index 117c786cdd..c1a17326a2 100644
--- a/django/contrib/gis/tests/geoapp/test_regress.py
+++ b/django/contrib/gis/tests/geoapp/test_regress.py
@@ -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
diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py
index 4fdbf8e8bc..085111a138 100644
--- a/django/contrib/gis/tests/geoapp/tests.py
+++ b/django/contrib/gis/tests/geoapp/tests.py
@@ -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.
diff --git a/django/contrib/gis/tests/relatedapp/tests.py b/django/contrib/gis/tests/relatedapp/tests.py
index 821d53f8a5..410216697b 100644
--- a/django/contrib/gis/tests/relatedapp/tests.py
+++ b/django/contrib/gis/tests/relatedapp/tests.py
@@ -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:
diff --git a/django/contrib/gis/tests/utils.py b/django/contrib/gis/tests/utils.py
index 29e17e7113..614dbe808d 100644
--- a/django/contrib/gis/tests/utils.py
+++ b/django/contrib/gis/tests/utils.py
@@ -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'
diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt
index c9e9f1fc28..6958135f88 100644
--- a/docs/ref/contrib/gis/db-api.txt
+++ b/docs/ref/contrib/gis/db-api.txt
@@ -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
diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt
index 935893265b..e5cecc230c 100644
--- a/docs/releases/1.8.txt
+++ b/docs/releases/1.8.txt
@@ -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`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^