~ubuntu-branches/debian/sid/python-django/sid

« back to all changes in this revision

Viewing changes to django/contrib/gis/db/backends/postgis/operations.py

  • Committer: Package Import Robot
  • Author(s): Luke Faraone
  • Date: 2013-11-07 15:33:49 UTC
  • mfrom: (1.3.12)
  • Revision ID: package-import@ubuntu.com-20131107153349-e31sc149l2szs3jb
Tags: 1.6-1
* New upstream version. Closes: #557474, #724637.
* python-django now also suggests the installation of ipython,
  bpython, python-django-doc, and libgdal1.
  Closes: #636511, #686333, #704203
* Set package maintainer to Debian Python Modules Team.
* Bump standards version to 3.9.5, no changes needed.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
from django.db.backends.postgresql_psycopg2.base import DatabaseOperations
12
12
from django.db.utils import DatabaseError
13
13
from django.utils import six
 
14
from django.utils.functional import cached_property
 
15
 
 
16
from .models import GeometryColumns, SpatialRefSys
 
17
 
14
18
 
15
19
#### Classes used in constructing PostGIS spatial SQL ####
16
20
class PostGISOperator(SpatialOperation):
62
66
    compiler_module = 'django.contrib.gis.db.models.sql.compiler'
63
67
    name = 'postgis'
64
68
    postgis = True
 
69
    geom_func_prefix = 'ST_'
65
70
    version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
66
71
    valid_aggregates = dict([(k, None) for k in
67
72
                             ('Collect', 'Extent', 'Extent3D', 'MakeLine', 'Union')])
72
77
    def __init__(self, connection):
73
78
        super(PostGISOperations, self).__init__(connection)
74
79
 
75
 
        # Trying to get the PostGIS version because the function
76
 
        # signatures will depend on the version used.  The cost
77
 
        # here is a database query to determine the version, which
78
 
        # can be mitigated by setting `POSTGIS_VERSION` with a 3-tuple
79
 
        # comprising user-supplied values for the major, minor, and
80
 
        # subminor revision of PostGIS.
81
 
        try:
82
 
            if hasattr(settings, 'POSTGIS_VERSION'):
83
 
                vtup = settings.POSTGIS_VERSION
84
 
                if len(vtup) == 3:
85
 
                    # The user-supplied PostGIS version.
86
 
                    version = vtup
87
 
                else:
88
 
                    # This was the old documented way, but it's stupid to
89
 
                    # include the string.
90
 
                    version = vtup[1:4]
91
 
            else:
92
 
                vtup = self.postgis_version_tuple()
93
 
                version = vtup[1:]
94
 
 
95
 
            # Getting the prefix -- even though we don't officially support
96
 
            # PostGIS 1.2 anymore, keeping it anyway in case a prefix change
97
 
            # for something else is necessary.
98
 
            if version >= (1, 2, 2):
99
 
                prefix = 'ST_'
100
 
            else:
101
 
                prefix = ''
102
 
 
103
 
            self.geom_func_prefix = prefix
104
 
            self.spatial_version = version
105
 
        except DatabaseError:
106
 
            raise ImproperlyConfigured(
107
 
                'Cannot determine PostGIS version for database "%s". '
108
 
                'GeoDjango requires at least PostGIS version 1.3. '
109
 
                'Was the database created from a spatial database '
110
 
                'template?' % self.connection.settings_dict['NAME']
111
 
                )
112
 
        # TODO: Raise helpful exceptions as they become known.
113
 
 
 
80
        prefix = self.geom_func_prefix
114
81
        # PostGIS-specific operators. The commented descriptions of these
115
82
        # operators come from Section 7.6 of the PostGIS 1.4 documentation.
116
83
        self.geometry_operators = {
188
155
        self.geometry_functions.update(self.distance_functions)
189
156
 
190
157
        # Only PostGIS versions 1.3.4+ have GeoJSON serialization support.
191
 
        if version < (1, 3, 4):
 
158
        if self.spatial_version < (1, 3, 4):
192
159
            GEOJSON = False
193
160
        else:
194
161
            GEOJSON = prefix + 'AsGeoJson'
195
162
 
196
163
        # ST_ContainsProperly ST_MakeLine, and ST_GeoHash added in 1.4.
197
 
        if version >= (1, 4, 0):
 
164
        if self.spatial_version >= (1, 4, 0):
198
165
            GEOHASH = 'ST_GeoHash'
199
166
            BOUNDINGCIRCLE = 'ST_MinimumBoundingCircle'
200
167
            self.geometry_functions['contains_properly'] = PostGISFunction(prefix, 'ContainsProperly')
202
169
            GEOHASH, BOUNDINGCIRCLE = False, False
203
170
 
204
171
        # Geography type support added in 1.5.
205
 
        if version >= (1, 5, 0):
 
172
        if self.spatial_version >= (1, 5, 0):
206
173
            self.geography = True
207
174
            # Only a subset of the operators and functions are available
208
175
            # for the geography type.
209
176
            self.geography_functions = self.distance_functions.copy()
210
177
            self.geography_functions.update({
211
 
                    'coveredby' : self.geometry_functions['coveredby'],
212
 
                    'covers' : self.geometry_functions['covers'],
213
 
                    'intersects' : self.geometry_functions['intersects'],
214
 
                    })
 
178
                'coveredby': self.geometry_functions['coveredby'],
 
179
                'covers': self.geometry_functions['covers'],
 
180
                'intersects': self.geometry_functions['intersects'],
 
181
            })
215
182
            self.geography_operators = {
216
 
                'bboverlaps' : PostGISOperator('&&'),
217
 
                }
 
183
                'bboverlaps': PostGISOperator('&&'),
 
184
            }
218
185
 
219
186
        # Native geometry type support added in PostGIS 2.0.
220
 
        if version >= (2, 0, 0):
 
187
        if self.spatial_version >= (2, 0, 0):
221
188
            self.geometry = True
222
189
 
223
190
        # Creating a dictionary lookup of all GIS terms for PostGIS.
224
 
        gis_terms = ['isnull']
225
 
        gis_terms += list(self.geometry_operators)
226
 
        gis_terms += list(self.geometry_functions)
227
 
        self.gis_terms = dict([(term, None) for term in gis_terms])
 
191
        self.gis_terms = set(['isnull'])
 
192
        self.gis_terms.update(self.geometry_operators)
 
193
        self.gis_terms.update(self.geometry_functions)
228
194
 
229
195
        self.area = prefix + 'Area'
230
196
        self.bounding_circle = BOUNDINGCIRCLE
247
213
        self.makeline = prefix + 'MakeLine'
248
214
        self.mem_size = prefix + 'mem_size'
249
215
        self.num_geom = prefix + 'NumGeometries'
250
 
        self.num_points =prefix + 'npoints'
 
216
        self.num_points = prefix + 'npoints'
251
217
        self.perimeter = prefix + 'Perimeter'
252
218
        self.point_on_surface = prefix + 'PointOnSurface'
253
219
        self.polygonize = prefix + 'Polygonize'
261
227
        self.union = prefix + 'Union'
262
228
        self.unionagg = prefix + 'Union'
263
229
 
264
 
        if version >= (2, 0, 0):
 
230
        if self.spatial_version >= (2, 0, 0):
265
231
            self.extent3d = prefix + '3DExtent'
266
232
            self.length3d = prefix + '3DLength'
267
233
            self.perimeter3d = prefix + '3DPerimeter'
270
236
            self.length3d = prefix + 'Length3D'
271
237
            self.perimeter3d = prefix + 'Perimeter3D'
272
238
 
 
239
    @cached_property
 
240
    def spatial_version(self):
 
241
        """Determine the version of the PostGIS library."""
 
242
        # Trying to get the PostGIS version because the function
 
243
        # signatures will depend on the version used.  The cost
 
244
        # here is a database query to determine the version, which
 
245
        # can be mitigated by setting `POSTGIS_VERSION` with a 3-tuple
 
246
        # comprising user-supplied values for the major, minor, and
 
247
        # subminor revision of PostGIS.
 
248
        if hasattr(settings, 'POSTGIS_VERSION'):
 
249
            version = settings.POSTGIS_VERSION
 
250
        else:
 
251
            try:
 
252
                vtup = self.postgis_version_tuple()
 
253
            except DatabaseError:
 
254
                raise ImproperlyConfigured(
 
255
                    'Cannot determine PostGIS version for database "%s". '
 
256
                    'GeoDjango requires at least PostGIS version 1.3. '
 
257
                    'Was the database created from a spatial database '
 
258
                    'template?' % self.connection.settings_dict['NAME']
 
259
                    )
 
260
            version = vtup[1:]
 
261
        return version
 
262
 
273
263
    def check_aggregate_support(self, aggregate):
274
264
        """
275
265
        Checks if the given aggregate name is supported (that is, if it's
324
314
                raise NotImplementedError('PostGIS 1.5 supports geography columns '
325
315
                                          'only with an SRID of 4326.')
326
316
 
327
 
            return 'geography(%s,%d)'% (f.geom_type, f.srid)
 
317
            return 'geography(%s,%d)' % (f.geom_type, f.srid)
328
318
        elif self.geometry:
329
319
            # Postgis 2.0 supports type-based geometries.
330
320
            # TODO: Support 'M' extension.
402
392
        """
403
393
        Helper routine for calling PostGIS functions and returning their result.
404
394
        """
405
 
        cursor = self.connection._cursor()
406
 
        try:
407
 
            try:
408
 
                cursor.execute('SELECT %s()' % func)
409
 
                row = cursor.fetchone()
410
 
            except:
411
 
                # Responsibility of callers to perform error handling.
412
 
                raise
413
 
        finally:
414
 
            # Close out the connection.  See #9437.
415
 
            self.connection.close()
416
 
        return row[0]
 
395
        # Close out the connection.  See #9437.
 
396
        with self.connection.temporary_connection() as cursor:
 
397
            cursor.execute('SELECT %s()' % func)
 
398
            return cursor.fetchone()[0]
417
399
 
418
400
    def postgis_geos_version(self):
419
401
        "Returns the version of the GEOS library used with PostGIS."
560
542
 
561
543
        elif lookup_type == 'isnull':
562
544
            # Handling 'isnull' lookup type
563
 
            return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
 
545
            return "%s IS %sNULL" % (geo_col, ('' if value else 'NOT ')), []
564
546
 
565
547
        raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
566
548
 
573
555
        if not self.check_aggregate_support(agg):
574
556
            raise NotImplementedError('%s spatial aggregate is not implmented for this backend.' % agg_name)
575
557
        agg_name = agg_name.lower()
576
 
        if agg_name == 'union': agg_name += 'agg'
 
558
        if agg_name == 'union':
 
559
            agg_name += 'agg'
577
560
        sql_template = '%(function)s(%(field)s)'
578
561
        sql_function = getattr(self, agg_name)
579
562
        return sql_template, sql_function
580
563
 
581
564
    # Routines for getting the OGC-compliant models.
582
565
    def geometry_columns(self):
583
 
        from django.contrib.gis.db.backends.postgis.models import GeometryColumns
584
566
        return GeometryColumns
585
567
 
586
568
    def spatial_ref_sys(self):
587
 
        from django.contrib.gis.db.backends.postgis.models import SpatialRefSys
588
569
        return SpatialRefSys