From b14bd604043ba1d89493558f4f6cb7a9bcf17923 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Mon, 27 May 2013 11:54:14 +0200 Subject: [PATCH] Reimplemented PostGIS spatial_version with cached_property --- .../gis/db/backends/postgis/operations.py | 81 ++++++++----------- 1 file changed, 35 insertions(+), 46 deletions(-) diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 734f39c752..84dbda3239 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -11,6 +11,10 @@ from django.core.exceptions import ImproperlyConfigured from django.db.backends.postgresql_psycopg2.base import DatabaseOperations from django.db.utils import DatabaseError from django.utils import six +from django.utils.functional import cached_property + +from .models import GeometryColumns, SpatialRefSys + #### Classes used in constructing PostGIS spatial SQL #### class PostGISOperator(SpatialOperation): @@ -62,6 +66,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): compiler_module = 'django.contrib.gis.db.models.sql.compiler' name = 'postgis' postgis = True + geom_func_prefix = 'ST_' version_regex = re.compile(r'^(?P\d)\.(?P\d)\.(?P\d+)') valid_aggregates = dict([(k, None) for k in ('Collect', 'Extent', 'Extent3D', 'MakeLine', 'Union')]) @@ -72,45 +77,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): def __init__(self, connection): super(PostGISOperations, self).__init__(connection) - # Trying to get the PostGIS version because the function - # signatures will depend on the version used. The cost - # here is a database query to determine the version, which - # can be mitigated by setting `POSTGIS_VERSION` with a 3-tuple - # comprising user-supplied values for the major, minor, and - # subminor revision of PostGIS. - try: - if hasattr(settings, 'POSTGIS_VERSION'): - vtup = settings.POSTGIS_VERSION - if len(vtup) == 3: - # The user-supplied PostGIS version. - version = vtup - else: - # This was the old documented way, but it's stupid to - # include the string. - version = vtup[1:4] - else: - vtup = self.postgis_version_tuple() - version = vtup[1:] - - # Getting the prefix -- even though we don't officially support - # PostGIS 1.2 anymore, keeping it anyway in case a prefix change - # for something else is necessary. - if version >= (1, 2, 2): - prefix = 'ST_' - else: - prefix = '' - - self.geom_func_prefix = prefix - self.spatial_version = version - except DatabaseError: - raise ImproperlyConfigured( - 'Cannot determine PostGIS version for database "%s". ' - 'GeoDjango requires at least PostGIS version 1.3. ' - 'Was the database created from a spatial database ' - 'template?' % self.connection.settings_dict['NAME'] - ) - # TODO: Raise helpful exceptions as they become known. - + prefix = self.geom_func_prefix # PostGIS-specific operators. The commented descriptions of these # operators come from Section 7.6 of the PostGIS 1.4 documentation. self.geometry_operators = { @@ -188,13 +155,13 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): self.geometry_functions.update(self.distance_functions) # Only PostGIS versions 1.3.4+ have GeoJSON serialization support. - if version < (1, 3, 4): + if self.spatial_version < (1, 3, 4): GEOJSON = False else: GEOJSON = prefix + 'AsGeoJson' # ST_ContainsProperly ST_MakeLine, and ST_GeoHash added in 1.4. - if version >= (1, 4, 0): + if self.spatial_version >= (1, 4, 0): GEOHASH = 'ST_GeoHash' BOUNDINGCIRCLE = 'ST_MinimumBoundingCircle' self.geometry_functions['contains_properly'] = PostGISFunction(prefix, 'ContainsProperly') @@ -202,7 +169,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): GEOHASH, BOUNDINGCIRCLE = False, False # Geography type support added in 1.5. - if version >= (1, 5, 0): + if self.spatial_version >= (1, 5, 0): self.geography = True # Only a subset of the operators and functions are available # for the geography type. @@ -217,7 +184,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): } # Native geometry type support added in PostGIS 2.0. - if version >= (2, 0, 0): + if self.spatial_version >= (2, 0, 0): self.geometry = True # Creating a dictionary lookup of all GIS terms for PostGIS. @@ -260,7 +227,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): self.union = prefix + 'Union' self.unionagg = prefix + 'Union' - if version >= (2, 0, 0): + if self.spatial_version >= (2, 0, 0): self.extent3d = prefix + '3DExtent' self.length3d = prefix + '3DLength' self.perimeter3d = prefix + '3DPerimeter' @@ -269,6 +236,30 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): self.length3d = prefix + 'Length3D' self.perimeter3d = prefix + 'Perimeter3D' + @cached_property + def spatial_version(self): + """Determine the version of the PostGIS library.""" + # Trying to get the PostGIS version because the function + # signatures will depend on the version used. The cost + # here is a database query to determine the version, which + # can be mitigated by setting `POSTGIS_VERSION` with a 3-tuple + # comprising user-supplied values for the major, minor, and + # subminor revision of PostGIS. + if hasattr(settings, 'POSTGIS_VERSION'): + version = settings.POSTGIS_VERSION + else: + try: + vtup = self.postgis_version_tuple() + except DatabaseError: + raise ImproperlyConfigured( + 'Cannot determine PostGIS version for database "%s". ' + 'GeoDjango requires at least PostGIS version 1.3. ' + 'Was the database created from a spatial database ' + 'template?' % self.connection.settings_dict['NAME'] + ) + version = vtup[1:] + return version + def check_aggregate_support(self, aggregate): """ Checks if the given aggregate name is supported (that is, if it's @@ -572,9 +563,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): # Routines for getting the OGC-compliant models. def geometry_columns(self): - from django.contrib.gis.db.backends.postgis.models import GeometryColumns return GeometryColumns def spatial_ref_sys(self): - from django.contrib.gis.db.backends.postgis.models import SpatialRefSys return SpatialRefSys