Commit b14bd604 authored by Claude Paroz's avatar Claude Paroz
Browse files

Reimplemented PostGIS spatial_version with cached_property

parent 749c2e43
Loading
Loading
Loading
Loading
+35 −46
Original line number Diff line number Diff line
@@ -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<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\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