Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

[soc2009/multidb] Fixed #11741 -- Updates to the spatial backends (e.…

…g., re-enabled POSTGIS_VERSION setting); added geometry backend module. Patch from Justin Bronn.

git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/multidb@11872 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 05b4d2f67bc3e75d2d1aada844d6031cc126e48e 1 parent 11c00d6
Alex Gaynor authored December 16, 2009
47  django/contrib/gis/db/backends/base.py
... ...
@@ -1,5 +1,6 @@
1 1
 """
2  
-
  2
+Base/mixin classes for the spatial backend database operations and the
  3
+`SpatialRefSys` model the backend.
3 4
 """
4 5
 import re
5 6
 from django.conf import settings
@@ -14,8 +15,9 @@ class BaseSpatialOperations(object):
14 15
     distance_functions = {}
15 16
     geometry_functions = {}
16 17
     geometry_operators = {}
  18
+    geography_operators = {}
  19
+    geography_functions = {}
17 20
     gis_terms = {}
18  
-    limited_where = {}
19 21
 
20 22
     # Quick booleans for the type of this spatial backend, and
21 23
     # an attribute for the spatial database version tuple (if applicable)
@@ -28,6 +30,9 @@ class BaseSpatialOperations(object):
28 30
     # How the geometry column should be selected.
29 31
     select = None
30 32
 
  33
+    # Does the spatial database have a geography type?
  34
+    geography = False
  35
+
31 36
     area = False
32 37
     centroid = False
33 38
     difference = False
@@ -37,11 +42,13 @@ class BaseSpatialOperations(object):
37 42
     envelope = False
38 43
     force_rhr = False
39 44
     mem_size = False
  45
+    bounding_circle = False
40 46
     num_geom = False
41 47
     num_points = False
42 48
     perimeter = False
43 49
     perimeter3d = False
44 50
     point_on_surface = False
  51
+    polygonize = False
45 52
     scale = False
46 53
     snap_to_grid = False
47 54
     sym_difference = False
@@ -67,11 +74,6 @@ class BaseSpatialOperations(object):
67 74
     from_text = False
68 75
     from_wkb = False
69 76
 
70  
-    def geo_quote_name(self, name):
71  
-        if isinstance(name, unicode):
72  
-            name = name.encode('ascii')
73  
-        return "'%s'" % name
74  
-
75 77
     # Default conversion functions for aggregates; will be overridden if implemented
76 78
     # for the spatial backend.
77 79
     def convert_extent(self, box):
@@ -83,6 +85,37 @@ def convert_extent3d(self, box):
83 85
     def convert_geom(self, geom_val, geom_field):
84 86
         raise NotImplementedError('Aggregate method not implemented for this spatial backend.')
85 87
 
  88
+    # For quoting column values, rather than columns.
  89
+    def geo_quote_name(self, name):
  90
+        if isinstance(name, unicode):
  91
+            name = name.encode('ascii')
  92
+        return "'%s'" % name
  93
+
  94
+    # GeometryField operations
  95
+    def geo_db_type(self, f):
  96
+        """
  97
+        Returns the database column type for the geometry field on
  98
+        the spatial backend.
  99
+        """
  100
+        raise NotImplementedError
  101
+
  102
+    def get_distance(self, f, value, lookup_type):
  103
+        """
  104
+        Returns the distance parameters for the given geometry field,
  105
+        lookup value, and lookup type.
  106
+        """
  107
+        raise NotImplementedError('Distance operations not available on this spatial backend.')
  108
+
  109
+    def get_geom_placeholder(self, f, value):
  110
+        """
  111
+        Returns the placeholder for the given geometry field with the given
  112
+        value.  Depending on the spatial backend, the placeholder may contain a
  113
+        stored procedure call to the transformation function of the spatial
  114
+        backend.
  115
+        """
  116
+        raise NotImplementedError
  117
+
  118
+    # Spatial SQL Construction
86 119
     def spatial_aggregate_sql(self, agg):
87 120
         raise NotImplementedError('Aggregate support not implemented for this spatial backend.')
88 121
 
6  django/contrib/gis/db/backends/mysql/operations.py
@@ -31,6 +31,9 @@ class MySQLOperations(DatabaseOperations, BaseSpatialOperations):
31 31
 
32 32
     gis_terms = dict([(term, None) for term in geometry_functions.keys() + ['isnull']])
33 33
 
  34
+    def geo_db_type(self, f):
  35
+        return f.geom_type
  36
+
34 37
     def get_geom_placeholder(self, value, srid):
35 38
         """
36 39
         The placeholder here has to include MySQL's WKT constructor.  Because
@@ -43,8 +46,7 @@ def get_geom_placeholder(self, value, srid):
43 46
             placeholder = '%s(%%s)' % self.from_text
44 47
         return placeholder
45 48
 
46  
-    def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
47  
-        qn = self.quote_name
  49
+    def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
48 50
         alias, col, db_type = lvalue
49 51
 
50 52
         geo_col = '%s.%s' % (qn(alias), qn(col))
2  django/contrib/gis/db/backends/oracle/base.py
@@ -7,4 +7,4 @@ class DatabaseWrapper(OracleDatabaseWrapper):
7 7
     def __init__(self, *args, **kwargs):
8 8
         super(DatabaseWrapper, self).__init__(*args, **kwargs)
9 9
         self.creation = OracleCreation(self)
10  
-        self.ops = OracleOperations()
  10
+        self.ops = OracleOperations(self)
24  django/contrib/gis/db/backends/oracle/compiler.py
@@ -7,7 +7,29 @@ class GeoSQLCompiler(BaseGeoSQLCompiler, SQLCompiler):
7 7
     pass
8 8
 
9 9
 class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler):
10  
-    pass
  10
+    def placeholder(self, field, val):
  11
+        if field is None:
  12
+            # A field value of None means the value is raw.
  13
+            return val
  14
+        elif hasattr(field, 'get_placeholder'):
  15
+            # Some fields (e.g. geo fields) need special munging before
  16
+            # they can be inserted.
  17
+            ph = field.get_placeholder(val, self.connection)
  18
+            if ph == 'NULL':
  19
+                # If the placeholder returned is 'NULL', then we need to
  20
+                # to remove None from the Query parameters. Specifically,
  21
+                # cx_Oracle will assume a CHAR type when a placeholder ('%s')
  22
+                # is used for columns of MDSYS.SDO_GEOMETRY.  Thus, we use
  23
+                # 'NULL' for the value, and remove None from the query params.
  24
+                # See also #10888.
  25
+                param_idx = self.query.columns.index(field.column)
  26
+                params = list(self.query.params)
  27
+                params.pop(param_idx)
  28
+                self.query.params = tuple(params)
  29
+            return ph
  30
+        else:
  31
+            # Return the common case for the placeholder
  32
+            return '%s'
11 33
 
12 34
 class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler):
13 35
     pass
66  django/contrib/gis/db/backends/oracle/operations.py
@@ -14,7 +14,7 @@
14 14
 from django.contrib.gis.db.backends.base import BaseSpatialOperations
15 15
 from django.contrib.gis.db.backends.oracle.adapter import OracleSpatialAdapter
16 16
 from django.contrib.gis.db.backends.util import SpatialFunction
17  
-from django.contrib.gis.geometry import Geometry
  17
+from django.contrib.gis.geometry.backend import Geometry
18 18
 from django.contrib.gis.measure import Distance
19 19
 
20 20
 class SDOOperation(SpatialFunction):
@@ -91,7 +91,7 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
91 91
     sym_difference = 'SDO_GEOM.SDO_XOR'
92 92
     transform = 'SDO_CS.TRANSFORM'
93 93
     union = 'SDO_GEOM.SDO_UNION'
94  
-    unionagg = 'SDO_AGGR_UNION'    
  94
+    unionagg = 'SDO_AGGR_UNION'
95 95
 
96 96
     # We want to get SDO Geometries as WKT because it is much easier to
97 97
     # instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.
@@ -128,6 +128,10 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
128 128
     gis_terms += geometry_functions.keys()
129 129
     gis_terms = dict([(term, None) for term in gis_terms])
130 130
 
  131
+    def __init__(self, connection):
  132
+        super(OracleOperations, self).__init__()
  133
+        self.connection = connection
  134
+
131 135
     def convert_extent(self, clob):
132 136
         if clob:
133 137
             # Generally, Oracle returns a polygon for the extent -- however,
@@ -156,7 +160,40 @@ def convert_geom(self, clob, geo_field):
156 160
         else:
157 161
             return None
158 162
 
159  
-    def get_geom_placeholder(self, value, srid):
  163
+    def geo_db_type(self, f):
  164
+        """
  165
+        Returns the geometry database type for Oracle.  Unlike other spatial
  166
+        backends, no stored procedure is necessary and it's the same for all
  167
+        geometry types.
  168
+        """
  169
+        return 'MDSYS.SDO_GEOMETRY'
  170
+
  171
+    def get_distance(self, f, value, lookup_type):
  172
+        """
  173
+        Returns the distance parameters given the value and the lookup type.
  174
+        On Oracle, geometry columns with a geodetic coordinate system behave
  175
+        implicitly like a geography column, and thus meters will be used as
  176
+        the distance parameter on them.
  177
+        """
  178
+        if not value:
  179
+            return []
  180
+        value = value[0]
  181
+        if isinstance(value, Distance):
  182
+            if f.geodetic(self.connection):
  183
+                dist_param = value.m
  184
+            else:
  185
+                dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
  186
+        else:
  187
+            dist_param = value
  188
+
  189
+        # dwithin lookups on oracle require a special string parameter
  190
+        # that starts with "distance=".
  191
+        if lookup_type == 'dwithin':
  192
+            dist_param = 'distance=%s' % dist_param
  193
+
  194
+        return [dist_param]
  195
+
  196
+    def get_geom_placeholder(self, f, value):
160 197
         """
161 198
         Provides a proper substitution value for Geometries that are not in the
162 199
         SRID of the field.  Specifically, this routine will substitute in the
@@ -165,26 +202,25 @@ def get_geom_placeholder(self, value, srid):
165 202
         if value is None:
166 203
             return 'NULL'
167 204
 
168  
-        def transform_value(value, srid):
169  
-            return value.srid != srid
  205
+        def transform_value(val, srid):
  206
+            return val.srid != srid
170 207
 
171 208
         if hasattr(value, 'expression'):
172  
-            if transform_value(value, srid):
173  
-                placeholder = '%s(%%s, %s)' % (self.transform, srid)
  209
+            if transform_value(value, f.srid):
  210
+                placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
174 211
             else:
175 212
                 placeholder = '%s'
176 213
             # No geometry value used for F expression, substitue in
177 214
             # the column name instead.
178 215
             return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
179 216
         else:
180  
-            if transform_value(value, srid):
181  
-                return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (self.transform, value.srid, srid)
  217
+            if transform_value(value, f.srid):
  218
+                return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (self.transform, value.srid, f.srid)
182 219
             else:
183  
-                return 'SDO_GEOMETRY(%%s, %s)' % srid
  220
+                return 'SDO_GEOMETRY(%%s, %s)' % f.srid
184 221
 
185  
-    def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
  222
+    def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
186 223
         "Returns the SQL WHERE clause for use in Oracle spatial SQL construction."
187  
-        qn = self.quote_name
188 224
         alias, col, db_type = lvalue
189 225
 
190 226
         # Getting the quoted table name as `geo_col`.
@@ -214,15 +250,15 @@ def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
214 250
                 if lookup_type == 'relate':
215 251
                     # The SDORelate class handles construction for these queries,
216 252
                     # and verifies the mask argument.
217  
-                    return sdo_op(value[1]).as_sql(geo_col, self.get_geom_placeholder(geom, field.srid))
  253
+                    return sdo_op(value[1]).as_sql(geo_col, self.get_geom_placeholder(field, geom))
218 254
                 else:
219 255
                     # Otherwise, just call the `as_sql` method on the SDOOperation instance.
220  
-                    return sdo_op.as_sql(geo_col, self.get_geom_placeholder(geom, field.srid))
  256
+                    return sdo_op.as_sql(geo_col, self.get_geom_placeholder(field, geom))
221 257
             else:
222 258
                 # Lookup info is a SDOOperation instance, whose `as_sql` method returns
223 259
                 # the SQL necessary for the geometry function call. For example:
224 260
                 #  SDO_CONTAINS("geoapp_country"."poly", SDO_GEOMTRY('POINT(5 23)', 4326)) = 'TRUE'
225  
-                return lookup_info.as_sql(geo_col, self.get_geom_placeholder(value, field.srid))
  261
+                return lookup_info.as_sql(geo_col, self.get_geom_placeholder(field, value))
226 262
         elif lookup_type == 'isnull':
227 263
             # Handling 'isnull' lookup type
228 264
             return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
45  django/contrib/gis/db/backends/postgis/creation.py
@@ -16,32 +16,43 @@ def sql_indexes_for_field(self, model, f, style):
16 16
             qn = self.connection.ops.quote_name
17 17
             db_table = model._meta.db_table
18 18
 
19  
-            output.append(style.SQL_KEYWORD('SELECT ') +
20  
-                          style.SQL_TABLE('AddGeometryColumn') + '(' +
21  
-                          style.SQL_TABLE(gqn(db_table)) + ', ' +
22  
-                          style.SQL_FIELD(gqn(f.column)) + ', ' +
23  
-                          style.SQL_FIELD(str(f.srid)) + ', ' +
24  
-                          style.SQL_COLTYPE(gqn(f.geom_type)) + ', ' +
25  
-                          style.SQL_KEYWORD(str(f.dim)) + ');')
26  
-
27  
-            if not f.null:
28  
-                # Add a NOT NULL constraint to the field
29  
-                output.append(style.SQL_KEYWORD('ALTER TABLE ') +
30  
-                              style.SQL_TABLE(qn(db_table)) +
31  
-                              style.SQL_KEYWORD(' ALTER ') +
32  
-                              style.SQL_FIELD(qn(f.column)) +
33  
-                              style.SQL_KEYWORD(' SET NOT NULL') + ';')
  19
+            if f.geography:
  20
+                # Geogrophy columns are created normally.
  21
+                pass
  22
+            else:
  23
+                # Geometry columns are created by `AddGeometryColumn`
  24
+                # stored procedure.
  25
+                output.append(style.SQL_KEYWORD('SELECT ') +
  26
+                              style.SQL_TABLE('AddGeometryColumn') + '(' +
  27
+                              style.SQL_TABLE(gqn(db_table)) + ', ' +
  28
+                              style.SQL_FIELD(gqn(f.column)) + ', ' +
  29
+                              style.SQL_FIELD(str(f.srid)) + ', ' +
  30
+                              style.SQL_COLTYPE(gqn(f.geom_type)) + ', ' +
  31
+                              style.SQL_KEYWORD(str(f.dim)) + ');')
  32
+
  33
+                if not f.null:
  34
+                    # Add a NOT NULL constraint to the field
  35
+                    output.append(style.SQL_KEYWORD('ALTER TABLE ') +
  36
+                                  style.SQL_TABLE(qn(db_table)) +
  37
+                                  style.SQL_KEYWORD(' ALTER ') +
  38
+                                  style.SQL_FIELD(qn(f.column)) +
  39
+                                  style.SQL_KEYWORD(' SET NOT NULL') + ';')
34 40
 
35 41
 
36 42
             if f.spatial_index:
  43
+                # Spatial indexes created the same way for both Geometry and
  44
+                # Geography columns
  45
+                if f.geography:
  46
+                    index_opts = ''
  47
+                else:
  48
+                    index_opts = ' ' + style.SQL_KEYWORD(self.geom_index_opts)
37 49
                 output.append(style.SQL_KEYWORD('CREATE INDEX ') +
38 50
                               style.SQL_TABLE(qn('%s_%s_id' % (db_table, f.column))) +
39 51
                               style.SQL_KEYWORD(' ON ') +
40 52
                               style.SQL_TABLE(qn(db_table)) +
41 53
                               style.SQL_KEYWORD(' USING ') +
42 54
                               style.SQL_COLTYPE(self.geom_index_type) + ' ( ' +
43  
-                              style.SQL_FIELD(qn(f.column)) + ' ' +
44  
-                              style.SQL_KEYWORD(self.geom_index_opts) + ' );')
  55
+                              style.SQL_FIELD(qn(f.column)) + index_opts + ' );')
45 56
         return output
46 57
 
47 58
     def sql_table_creation_suffix(self):
208  django/contrib/gis/db/backends/postgis/operations.py
... ...
@@ -1,12 +1,15 @@
1 1
 import re
2 2
 from decimal import Decimal
3 3
 
4  
-from django.db.backends.postgresql.operations import DatabaseOperations
  4
+from django.conf import settings
5 5
 from django.contrib.gis.db.backends.base import BaseSpatialOperations
6 6
 from django.contrib.gis.db.backends.util import SpatialOperation, SpatialFunction
7 7
 from django.contrib.gis.db.backends.postgis.adapter import PostGISAdapter
8  
-from django.contrib.gis.geometry import Geometry
  8
+from django.contrib.gis.geometry.backend import Geometry
9 9
 from django.contrib.gis.measure import Distance
  10
+from django.core.exceptions import ImproperlyConfigured
  11
+from django.db.backends.postgresql.operations import DatabaseOperations
  12
+from django.db.backends.postgresql_psycopg2.base import Database
10 13
 
11 14
 #### Classes used in constructing PostGIS spatial SQL ####
12 15
 class PostGISOperator(SpatialOperation):
@@ -68,23 +71,48 @@ def __init__(self, connection):
68 71
         super(PostGISOperations, self).__init__(connection)
69 72
 
70 73
         # Trying to get the PostGIS version because the function
71  
-        # signatures will depend on the version used.
  74
+        # signatures will depend on the version used.  The cost
  75
+        # here is a database query to determine the version, which
  76
+        # can be mitigated by setting `POSTGIS_VERSION` with a 3-tuple
  77
+        # comprising user-supplied values for the major, minor, and
  78
+        # subminor revision of PostGIS.
72 79
         try:
73  
-            vtup = self.postgis_version_tuple()
74  
-            version = vtup[1:]
  80
+            if hasattr(settings, 'POSTGIS_VERSION'):
  81
+                vtup = settings.POSTGIS_VERSION
  82
+                if len(vtup) == 3:
  83
+                    # The user-supplied PostGIS version.
  84
+                    version = vtup
  85
+                else:
  86
+                    # This was the old documented way, but it's stupid to
  87
+                    # include the string.
  88
+                    version = vtup[1:4]
  89
+            else:
  90
+                vtup = self.postgis_version_tuple()
  91
+                version = vtup[1:]
  92
+
  93
+            # Getting the prefix -- even though we don't officially support
  94
+            # PostGIS 1.2 anymore, keeping it anyway in case a prefix change
  95
+            # for something else is necessary.
75 96
             if version >= (1, 2, 2):
76 97
                 prefix = 'ST_'
77 98
             else:
78 99
                 prefix = ''
  100
+
79 101
             self.geom_func_prefix = prefix
80 102
             self.spatial_version = version
  103
+        except Database.ProgrammingError:
  104
+            raise ImproperlyConfigured('Cannot determine PostGIS version for database "%s". '
  105
+                                       'GeoDjango requires at least PostGIS version 1.3. '
  106
+                                       'Was the database created from a spatial database '
  107
+                                       'template?' % self.connection.settings_dict['NAME']
  108
+                                       )
81 109
         except Exception, e:
82  
-            # TODO: Plain raising right now.
  110
+            # TODO: Raise helpful exceptions as they become known.
83 111
             raise
84 112
 
85 113
         # PostGIS-specific operators. The commented descriptions of these
86 114
         # operators come from Section 7.6 of the PostGIS 1.4 documentation.
87  
-        self.spatial_operators = {
  115
+        self.geometry_operators = {
88 116
             # The "&<" operator returns true if A's bounding box overlaps or
89 117
             # is to the left of B's bounding box.
90 118
             'overlaps_left' : PostGISOperator('&<'),
@@ -166,19 +194,6 @@ def get_dist_ops(operator):
166 194
         # Adding the distance functions to the geometries lookup.
167 195
         self.geometry_functions.update(self.distance_functions)
168 196
 
169  
-        # ST_ContainsProperly and GeoHash serialization added in 1.4.
170  
-        if version >= (1, 4, 0):
171  
-            GEOHASH = 'ST_GeoHash'
172  
-            self.geometry_functions['contains_properly'] = PostGISFunction(prefix, 'ContainsProperly')
173  
-        else:
174  
-            GEOHASH = False
175  
-
176  
-        # Creating a dictionary lookup of all GIS terms for PostGIS.
177  
-        gis_terms = ['isnull']
178  
-        gis_terms += self.spatial_operators.keys()
179  
-        gis_terms += self.geometry_functions.keys()
180  
-        self.gis_terms = dict([(term, None) for term in gis_terms])
181  
-
182 197
         # The union aggregate and topology operation use the same signature
183 198
         # in versions 1.3+.
184 199
         if version < (1, 3, 0):
@@ -194,7 +209,40 @@ def get_dist_ops(operator):
194 209
         else:
195 210
             GEOJSON = prefix + 'AsGeoJson'
196 211
 
  212
+        # ST_ContainsProperly ST_MakeLine, and ST_GeoHash added in 1.4.
  213
+        if version >= (1, 4, 0):
  214
+            GEOHASH = 'ST_GeoHash'
  215
+            MAKELINE = 'ST_MakeLine'
  216
+            BOUNDINGCIRCLE = 'ST_MinimumBoundingCircle'
  217
+            self.geometry_functions['contains_properly'] = PostGISFunction(prefix, 'ContainsProperly')
  218
+        else:
  219
+            GEOHASH, MAKELINE, BOUNDINGCIRCLE = False, False, False
  220
+
  221
+        # Geography type support added in 1.5.
  222
+        if version >= (1, 5, 0):
  223
+            self.geography = True
  224
+            # Only a subset of the operators and functions are available
  225
+            # for the geography type.
  226
+            self.geography_functions = self.distance_functions.copy()
  227
+            self.geography_functions.update({
  228
+                    'coveredby' : self.geometry_functions['coveredby'],
  229
+                    'covers' : self.geometry_functions['covers'],
  230
+                    'intersects' : self.geometry_functions['intersects'],
  231
+                    })
  232
+            self.geography_operators = {
  233
+                'bboverlaps' : PostGISOperator('&&'),
  234
+                'exact' : PostGISOperator('~='),
  235
+                'same_as' : PostGISOperator('~='),
  236
+                }
  237
+
  238
+        # Creating a dictionary lookup of all GIS terms for PostGIS.
  239
+        gis_terms = ['isnull']
  240
+        gis_terms += self.geometry_operators.keys()
  241
+        gis_terms += self.geometry_functions.keys()
  242
+        self.gis_terms = dict([(term, None) for term in gis_terms])
  243
+
197 244
         self.area = prefix + 'Area'
  245
+        self.bounding_circle = BOUNDINGCIRCLE
198 246
         self.centroid = prefix + 'Centroid'
199 247
         self.collect = prefix + 'Collect'
200 248
         self.difference = prefix + 'Difference'
@@ -212,13 +260,14 @@ def get_dist_ops(operator):
212 260
         self.length = prefix + 'Length'
213 261
         self.length3d = prefix + 'Length3D'
214 262
         self.length_spheroid = prefix + 'length_spheroid'
215  
-        self.makeline = prefix + 'MakeLine'
  263
+        self.makeline = MAKELINE
216 264
         self.mem_size = prefix + 'mem_size'
217 265
         self.num_geom = prefix + 'NumGeometries'
218 266
         self.num_points =prefix + 'npoints'
219 267
         self.perimeter = prefix + 'Perimeter'
220 268
         self.perimeter3d = prefix + 'Perimeter3D'
221 269
         self.point_on_surface = prefix + 'PointOnSurface'
  270
+        self.polygonize = prefix + 'Polygonize'
222 271
         self.scale = prefix + 'Scale'
223 272
         self.snap_to_grid = prefix + 'SnapToGrid'
224 273
         self.svg = prefix + 'AsSVG'
@@ -237,16 +286,22 @@ def check_aggregate_support(self, aggregate):
237 286
         return agg_name in self.valid_aggregates
238 287
 
239 288
     def convert_extent(self, box):
240  
-        # Box text will be something like "BOX(-90.0 30.0, -85.0 40.0)";
241  
-        # parsing out and returning as a 4-tuple.
  289
+        """
  290
+        Returns a 4-tuple extent for the `Extent` aggregate by converting
  291
+        the bounding box text returned by PostGIS (`box` argument), for
  292
+        example: "BOX(-90.0 30.0, -85.0 40.0)".
  293
+        """
242 294
         ll, ur = box[4:-1].split(',')
243 295
         xmin, ymin = map(float, ll.split())
244 296
         xmax, ymax = map(float, ur.split())
245 297
         return (xmin, ymin, xmax, ymax)
246 298
 
247 299
     def convert_extent3d(self, box3d):
248  
-        # Box text will be something like "BOX3D(-90.0 30.0 1, -85.0 40.0 2)";
249  
-        # parsing out and returning as a 4-tuple.
  300
+        """
  301
+        Returns a 6-tuple extent for the `Extent3D` aggregate by converting
  302
+        the 3d bounding-box text returnded by PostGIS (`box3d` argument), for
  303
+        example: "BOX3D(-90.0 30.0 1, -85.0 40.0 2)".
  304
+        """
250 305
         ll, ur = box3d[6:-1].split(',')
251 306
         xmin, ymin, zmin = map(float, ll.split())
252 307
         xmax, ymax, zmax = map(float, ur.split())
@@ -261,17 +316,78 @@ def convert_geom(self, hex, geo_field):
261 316
         else:
262 317
             return None
263 318
 
264  
-    def get_geom_placeholder(self, value, srid):
  319
+    def geo_db_type(self, f):
  320
+        """
  321
+        Return the database field type for the given geometry field.
  322
+        Typically this is `None` because geometry columns are added via
  323
+        the `AddGeometryColumn` stored procedure, unless the field
  324
+        has been specified to be of geography type instead.
  325
+        """
  326
+        if f.geography:
  327
+            if not self.geography:
  328
+                raise NotImplementedError('PostGIS 1.5 required for geography column support.')
  329
+
  330
+            if f.srid != 4326:
  331
+                raise NotImplementedError('PostGIS 1.5 supports geography columns '
  332
+                                          'only with an SRID of 4326.')
  333
+
  334
+            return 'geography(%s,%d)'% (f.geom_type, f.srid)
  335
+        else:
  336
+            return None
  337
+
  338
+    def get_distance(self, f, dist_val, lookup_type):
  339
+        """
  340
+        Retrieve the distance parameters for the given geometry field,
  341
+        distance lookup value, and the distance lookup type.
  342
+
  343
+        This is the most complex implementation of the spatial backends due to
  344
+        what is supported on geodetic geometry columns vs. what's available on
  345
+        projected geometry columns.  In addition, it has to take into account
  346
+        the newly introduced geography column type introudced in PostGIS 1.5.
  347
+        """
  348
+        # Getting the distance parameter and any options.
  349
+        if len(dist_val) == 1:
  350
+            value, option = dist_val[0], None
  351
+        else:
  352
+            value, option = dist_val
  353
+
  354
+        # Shorthand boolean flags.
  355
+        geodetic = f.geodetic(self.connection)
  356
+        geography = f.geography and self.geography
  357
+
  358
+        if isinstance(value, Distance):
  359
+            if geography:
  360
+                dist_param = value.m
  361
+            elif geodetic:
  362
+                if lookup_type == 'dwithin':
  363
+                    raise ValueError('Only numeric values of degree units are '
  364
+                                     'allowed on geographic DWithin queries.')
  365
+                dist_param = value.m
  366
+            else:
  367
+                dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
  368
+        else:
  369
+            # Assuming the distance is in the units of the field.
  370
+            dist_param = value
  371
+
  372
+        if (not geography and geodetic and lookup_type != 'dwithin'
  373
+            and option == 'spheroid'):
  374
+            # using distance_spheroid requires the spheroid of the field as
  375
+            # a parameter.
  376
+            return [f._spheroid, dist_param]
  377
+        else:
  378
+            return [dist_param]
  379
+
  380
+    def get_geom_placeholder(self, f, value):
265 381
         """
266 382
         Provides a proper substitution value for Geometries that are not in the
267 383
         SRID of the field.  Specifically, this routine will substitute in the
268 384
         ST_Transform() function call.
269 385
         """
270  
-        if value is None or value.srid == srid:
  386
+        if value is None or value.srid == f.srid:
271 387
             placeholder = '%s'
272 388
         else:
273 389
             # Adding Transform() to the SQL placeholder.
274  
-            placeholder = '%s(%%s, %s)' % (self.transform, srid)
  390
+            placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
275 391
 
276 392
         if hasattr(value, 'expression'):
277 393
             # If this is an F expression, then we don't really want
@@ -290,7 +406,7 @@ def _get_postgis_func(self, func):
290 406
             cursor.execute('SELECT %s()' % func)
291 407
             row = cursor.fetchone()
292 408
         except:
293  
-            # TODO: raise helpful exception here.
  409
+            # Responsibility of callers to perform error handling.
294 410
             raise
295 411
         finally:
296 412
             cursor.close()
@@ -334,32 +450,42 @@ def postgis_version_tuple(self):
334 450
 
335 451
         return (version, major, minor1, minor2)
336 452
 
337  
-    def num_params(self, lookup_type, val):
338  
-        def exactly_two(val): return val == 2
339  
-        def two_to_three(val): return val >= 2 and val <=3
  453
+    def num_params(self, lookup_type, num_param):
  454
+        """
  455
+        Helper routine that returns a boolean indicating whether the number of
  456
+        parameters is correct for the lookup type.
  457
+        """
  458
+        def exactly_two(np): return np == 2
  459
+        def two_to_three(np): return np >= 2 and np <=3
340 460
         if (lookup_type in self.distance_functions and
341 461
             lookup_type != 'dwithin'):
342  
-            return two_to_three(val)
  462
+            return two_to_three(num_param)
343 463
         else:
344  
-            return exactly_two(val)
  464
+            return exactly_two(num_param)
345 465
 
346  
-    def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
  466
+    def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
347 467
         """
348 468
         Constructs spatial SQL from the given lookup value tuple a
349 469
         (alias, col, db_type), the lookup type string, lookup value, and
350 470
         the geometry field.
351 471
         """
352  
-        qn = self.quote_name
353 472
         alias, col, db_type = lvalue
354 473
 
355 474
         # Getting the quoted geometry column.
356 475
         geo_col = '%s.%s' % (qn(alias), qn(col))
357 476
 
358  
-        if lookup_type in self.spatial_operators:
  477
+        if lookup_type in self.geometry_operators:
  478
+            if field.geography and not lookup_type in self.geography_operators:
  479
+                raise ValueError('PostGIS geography does not support the '
  480
+                                 '"%s" lookup.' % lookup_type)
359 481
             # Handling a PostGIS operator.
360  
-            op = self.spatial_operators[lookup_type]
361  
-            return op.as_sql(geo_col, self.get_geom_placeholder(value, field.srid))
  482
+            op = self.geometry_operators[lookup_type]
  483
+            return op.as_sql(geo_col, self.get_geom_placeholder(field, value))
362 484
         elif lookup_type in self.geometry_functions:
  485
+            if field.geography and not lookup_type in self.geography_functions:
  486
+                raise ValueError('PostGIS geography type does not support the '
  487
+                                 '"%s" lookup.' % lookup_type)
  488
+
363 489
             # See if a PostGIS geometry function matches the lookup type.
364 490
             tmp = self.geometry_functions[lookup_type]
365 491
 
@@ -392,7 +518,7 @@ def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
392 518
                 if lookup_type == 'relate':
393 519
                     op = op(self.geom_func_prefix, value[1])
394 520
                 elif lookup_type in self.distance_functions and lookup_type != 'dwithin':
395  
-                    if field.geodetic(self.connection):
  521
+                    if not field.geography and field.geodetic(self.connection):
396 522
                         # Geodetic distances are only availble from Points to PointFields.
397 523
                         if field.geom_type != 'POINT':
398 524
                             raise ValueError('PostGIS spherical operations are only valid on PointFields.')
@@ -412,7 +538,7 @@ def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
412 538
                 geom = value
413 539
 
414 540
             # Calling the `as_sql` function on the operation instance.
415  
-            return op.as_sql(geo_col, self.get_geom_placeholder(geom, field.srid))
  541
+            return op.as_sql(geo_col, self.get_geom_placeholder(field, geom))
416 542
 
417 543
         elif lookup_type == 'isnull':
418 544
             # Handling 'isnull' lookup type
60  django/contrib/gis/db/backends/spatialite/operations.py
@@ -4,7 +4,7 @@
4 4
 from django.contrib.gis.db.backends.base import BaseSpatialOperations
5 5
 from django.contrib.gis.db.backends.util import SpatialOperation, SpatialFunction
6 6
 from django.contrib.gis.db.backends.spatialite.adapter import SpatiaLiteAdapter
7  
-from django.contrib.gis.geometry import Geometry
  7
+from django.contrib.gis.geometry.backend import Geometry
8 8
 from django.contrib.gis.measure import Distance
9 9
 from django.core.exceptions import ImproperlyConfigured
10 10
 from django.db.backends.sqlite3.base import DatabaseOperations
@@ -119,11 +119,17 @@ def __init__(self, connection):
119 119
         try:
120 120
             vtup = self.spatialite_version_tuple()
121 121
             version = vtup[1:]
122  
-            self.spatial_version = version
123 122
             if version < (2, 3, 1):
124  
-                raise Exception('GeoDjango only supports SpatiaLite versions 2.3.1+')
125  
-        except Exception, e:
  123
+                raise ImproperlyConfigured('GeoDjango only supports SpatiaLite versions '
  124
+                                           '2.3.1 and above')
  125
+            self.spatial_version = version
  126
+        except ImproperlyConfigured:
126 127
             raise
  128
+        except Exception, msg:
  129
+            raise ImproperlyConfigured('Cannot determine the SpatiaLite version for the "%s" '
  130
+                                       'database (error was "%s").  Was the SpatiaLite initialization '
  131
+                                       'SQL loaded on this database?' %
  132
+                                       (self.connection.settings_dict['NAME'], msg))
127 133
 
128 134
         # Creating the GIS terms dictionary.
129 135
         gis_terms = ['isnull']
@@ -147,7 +153,36 @@ def convert_geom(self, wkt, geo_field):
147 153
         else:
148 154
             return None
149 155
 
150  
-    def get_geom_placeholder(self, value, srid):
  156
+    def geo_db_type(self, f):
  157
+        """
  158
+        Returns None because geometry columnas are added via the
  159
+        `AddGeometryColumn` stored procedure on SpatiaLite.
  160
+        """
  161
+        return None
  162
+
  163
+    def get_distance(self, f, value, lookup_type):
  164
+        """
  165
+        Returns the distance parameters for the given geometry field,
  166
+        lookup value, and lookup type.  SpatiaLite only supports regular
  167
+        cartesian-based queries (no spheroid/sphere calculations for point
  168
+        geometries like PostGIS).
  169
+        """
  170
+        if not value:
  171
+            return []
  172
+        value = value[0]
  173
+        if isinstance(value, Distance):
  174
+            if f.geodetic(self.connection):
  175
+                raise ValueError('SpatiaLite does not support distance queries on '
  176
+                                 'geometry fields with a geodetic coordinate system. '
  177
+                                 'Distance objects; use a numeric value of your '
  178
+                                 'distance in degrees instead.')
  179
+            else:
  180
+                dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
  181
+        else:
  182
+            dist_param = value
  183
+        return [dist_param]
  184
+
  185
+    def get_geom_placeholder(self, f, value):
151 186
         """
152 187
         Provides a proper substitution value for Geometries that are not in the
153 188
         SRID of the field.  Specifically, this routine will substitute in the
@@ -156,19 +191,19 @@ def get_geom_placeholder(self, value, srid):
156 191
         def transform_value(value, srid):
157 192
             return not (value is None or value.srid == srid)
158 193
         if hasattr(value, 'expression'):
159  
-            if transform_value(value, srid):
160  
-                placeholder = '%s(%%s, %s)' % (self.transform, srid)
  194
+            if transform_value(value, f.srid):
  195
+                placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
161 196
             else:
162 197
                 placeholder = '%s'
163 198
             # No geometry value used for F expression, substitue in
164 199
             # the column name instead.
165 200
             return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
166 201
         else:
167  
-            if transform_value(value, srid):
  202
+            if transform_value(value, f.srid):
168 203
                 # Adding Transform() to the SQL placeholder.
169  
-                return '%s(%s(%%s,%s), %s)' % (self.transform, self.from_text, value.srid, srid)
  204
+                return '%s(%s(%%s,%s), %s)' % (self.transform, self.from_text, value.srid, f.srid)
170 205
             else:
171  
-                return '%s(%%s,%s)' % (self.from_text, srid)
  206
+                return '%s(%%s,%s)' % (self.from_text, f.srid)
172 207
 
173 208
     def _get_spatialite_func(self, func):
174 209
         """
@@ -229,13 +264,12 @@ def spatial_aggregate_sql(self, agg):
229 264
         sql_function = getattr(self, agg_name)
230 265
         return sql_template, sql_function
231 266
 
232  
-    def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
  267
+    def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
233 268
         """
234 269
         Returns the SpatiaLite-specific SQL for the given lookup value
235 270
         [a tuple of (alias, column, db_type)], lookup type, lookup
236 271
         value, and the model field.
237 272
         """
238  
-        qn = self.quote_name
239 273
         alias, col, db_type = lvalue
240 274
 
241 275
         # Getting the quoted field as `geo_col`.
@@ -278,7 +312,7 @@ def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
278 312
                 op = tmp
279 313
                 geom = value
280 314
             # Calling the `as_sql` function on the operation instance.
281  
-            return op.as_sql(geo_col, self.get_geom_placeholder(geom, field.srid))
  315
+            return op.as_sql(geo_col, self.get_geom_placeholder(field, geom))
282 316
         elif lookup_type == 'isnull':
283 317
             # Handling 'isnull' lookup type
284 318
             return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
69  django/contrib/gis/db/models/fields.py
... ...
@@ -1,7 +1,7 @@
1 1
 from django.db.models.fields import Field
2 2
 from django.contrib.gis import forms
3 3
 from django.contrib.gis.db.models.proxy import GeometryProxy
4  
-from django.contrib.gis.geometry import Geometry, GeometryException
  4
+from django.contrib.gis.geometry.backend import Geometry, GeometryException
5 5
 from django.contrib.gis.measure import Distance
6 6
 from django.db.models.sql.expressions import SQLEvaluator
7 7
 
@@ -40,8 +40,8 @@ def get_srid_info(srid, connection):
40 40
 
41 41
     return _srid_cache[name][srid]
42 42
 
43  
-class GeometryField(SpatialBackend.Field):
44  
-    """The base GIS field -- maps to the OpenGIS Specification Geometry type."""
  43
+class GeometryField(Field):
  44
+    "The base GIS field -- maps to the OpenGIS Specification Geometry type."
45 45
 
46 46
     # The OpenGIS Geometry name.
47 47
     geom_type = 'GEOMETRY'
@@ -50,7 +50,7 @@ class GeometryField(SpatialBackend.Field):
50 50
     geodetic_units = ('Decimal Degree', 'degree')
51 51
 
52 52
     def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2,
53  
-                 **kwargs):
  53
+                 geography=False, **kwargs):
54 54
         """
55 55
         The initialization function for geometry fields.  Takes the following
56 56
         as keyword arguments:
@@ -67,8 +67,14 @@ def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2,
67 67
         dim:
68 68
          The number of dimensions for this geometry.  Defaults to 2.
69 69
 
70  
-        Oracle-specific keywords:
71  
-         extent, tolerance.
  70
+        extent:
  71
+         Customize the extent, in a 4-tuple of WGS 84 coordinates, for the
  72
+         geometry field entry in the `USER_SDO_GEOM_METADATA` table.  Defaults
  73
+         to (-180.0, -90.0, 180.0, 90.0).
  74
+
  75
+        tolerance:
  76
+         Define the tolerance, in meters, to use for the geometry field
  77
+         entry in the `USER_SDO_GEOM_METADATA` table.  Defaults to 0.05.
72 78
         """
73 79
 
74 80
         # Setting the index flag with the value of the `spatial_index` keyword.
@@ -85,6 +91,9 @@ def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2,
85 91
         # first parameter, so this works like normal fields.
86 92
         kwargs['verbose_name'] = verbose_name
87 93
 
  94
+        # Is this a geography rather than a geometry column?
  95
+        self.geography = geography
  96
+
88 97
         # Oracle-specific private attributes for creating the entrie in
89 98
         # `USER_SDO_GEOM_METADATA`
90 99
         self._extent = kwargs.pop('extent', (-180.0, -90.0, 180.0, 90.0))
@@ -121,17 +130,13 @@ def geodetic(self, connection):
121 130
         """
122 131
         return self.units_name(connection) in self.geodetic_units
123 132
 
124  
-    def get_distance(self, dist_val, lookup_type, connection):
  133
+    def get_distance(self, value, lookup_type, connection):
125 134
         """
126 135
         Returns a distance number in units of the field.  For example, if
127 136
         `D(km=1)` was passed in and the units of the field were in meters,
128 137
         then 1000 would be returned.
129 138
         """
130  
-        # Getting the distance parameter and any options.
131  
-        if len(dist_val) == 1:
132  
-            dist, option = dist_val[0], None
133  
-        else: 
134  
-            dist, option = dist_val
  139
+        return connection.ops.get_distance(self, value, lookup_type)
135 140
 
136 141
         if isinstance(dist, Distance):
137 142
             if self.geodetic(connection):
@@ -149,7 +154,7 @@ def get_distance(self, dist_val, lookup_type, connection):
149 154
 
150 155
         if connection.ops.oracle and lookup_type == 'dwithin':
151 156
             dist_param = 'distance=%s' % dist_param
152  
-            
  157
+
153 158
         if connection.ops.postgis and self.geodetic(connection) and lookup_type != 'dwithin' and option == 'spheroid':
154 159
             # On PostGIS, by default `ST_distance_sphere` is used; but if the
155 160
             # accuracy of `ST_distance_spheroid` is needed than the spheroid
@@ -179,11 +184,11 @@ def get_prep_value(self, value):
179 184
         # from the given string input.
180 185
         if isinstance(geom, Geometry):
181 186
             pass
182  
-        elif isinstance(geom, basestring):
  187
+        elif isinstance(geom, basestring) or hasattr(geom, '__geo_interface__'):
183 188
             try:
184 189
                 geom = Geometry(geom)
185 190
             except GeometryException:
186  
-                raise ValueError('Could not create geometry from lookup value: %s' % str(value))
  191
+                raise ValueError('Could not create geometry from lookup value.')
187 192
         else:
188 193
             raise ValueError('Cannot use parameter of `%s` type as lookup parameter.' % type(value))
189 194
 
@@ -217,17 +222,7 @@ def contribute_to_class(self, cls, name):
217 222
         setattr(cls, self.attname, GeometryProxy(Geometry, self))
218 223
 
219 224
     def db_type(self, connection):
220  
-        if (connection.ops.postgis or
221  
-            connection.ops.spatialite):
222  
-            # Geometry columns on these spatial backends are initialized via
223  
-            # the `AddGeometryColumn` stored procedure.
224  
-            return None
225  
-        elif connection.ops.mysql:
226  
-            return self.geom_type
227  
-        elif connection.ops.oracle:
228  
-            return 'MDSYS.SDO_GEOMETRY'
229  
-        else:
230  
-            raise NotImplementedError
  225
+        return connection.ops.geo_db_type(self)
231 226
 
232 227
     def formfield(self, **kwargs):
233 228
         defaults = {'form_class' : forms.GeometryField,
@@ -240,7 +235,11 @@ def formfield(self, **kwargs):
240 235
 
241 236
     def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
242 237
         """
243  
-        XXX: Document me.
  238
+        Prepare for the database lookup, and return any spatial parameters
  239
+        necessary for the query.  This includes wrapping any geometry
  240
+        parameters with a backend-specific adapter and formatting any distance
  241
+        parameters into the correct units for the coordinate system of the
  242
+        field.
244 243
         """
245 244
         if lookup_type in connection.ops.gis_terms:
246 245
             # special case for isnull lookup
@@ -254,8 +253,6 @@ def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
254 253
                 if lookup_type in connection.ops.distance_functions:
255 254
                     # Getting the distance parameter in the units of the field.
256 255
                     params += self.get_distance(value[1:], lookup_type, connection)
257  
-                elif lookup_type in connection.ops.limited_where:
258  
-                    pass
259 256
                 else:
260 257
                     params += value[1:]
261 258
             elif isinstance(value, SQLEvaluator):
@@ -281,33 +278,31 @@ def get_db_prep_save(self, value, connection):
281 278
             return connection.ops.Adapter(self.get_prep_value(value))
282 279
 
283 280
     def get_placeholder(self, value, connection):
284  
-        return connection.ops.get_geom_placeholder(value, self.srid)
  281
+        """
  282
+        Returns the placeholder for the geometry column for the
  283
+        given value.
  284
+        """
  285
+        return connection.ops.get_geom_placeholder(self, value)
285 286
 
286 287
 # The OpenGIS Geometry Type Fields
287 288
 class PointField(GeometryField):
288  
-    """Point"""
289 289
     geom_type = 'POINT'
290 290
 
291 291
 class LineStringField(GeometryField):
292  
-    """Line string"""
293 292
     geom_type = 'LINESTRING'
294 293
 
295 294
 class PolygonField(GeometryField):
296  
-    """Polygon"""
297 295
     geom_type = 'POLYGON'
298 296
 
299 297
 class MultiPointField(GeometryField):
300  
-    """Multi-point"""
301 298
     geom_type = 'MULTIPOINT'
302 299
 
303 300
 class MultiLineStringField(GeometryField):
304  
-    """Multi-line string"""
305 301
     geom_type = 'MULTILINESTRING'
306 302
 
307 303
 class MultiPolygonField(GeometryField):
308  
-    """Multi polygon"""
309 304
     geom_type = 'MULTIPOLYGON'
310 305
 
311 306
 class GeometryCollectionField(GeometryField):
312  
-    """Geometry collection"""
313 307
     geom_type = 'GEOMETRYCOLLECTION'
  308
+
6  django/contrib/gis/db/models/manager.py
... ...
@@ -1,6 +1,5 @@
1 1
 from django.db.models.manager import Manager
2 2
 from django.contrib.gis.db.models.query import GeoQuerySet
3  
-from django.contrib.gis.db.models.sql.subqueries import insert_query
4 3
 
5 4
 class GeoManager(Manager):
6 5
     "Overrides Manager to return Geographic QuerySets."
@@ -54,7 +53,7 @@ def length(self, *args, **kwargs):
54 53
 
55 54
     def make_line(self, *args, **kwargs):
56 55
         return self.get_query_set().make_line(*args, **kwargs)
57  
-    
  56
+
58 57
     def mem_size(self, *args, **kwargs):
59 58
         return self.get_query_set().mem_size(*args, **kwargs)
60 59
 
@@ -93,6 +92,3 @@ def union(self, *args, **kwargs):
93 92
 
94 93
     def unionagg(self, *args, **kwargs):
95 94
         return self.get_query_set().unionagg(*args, **kwargs)
96  
-
97  
-    def _insert(self, values, **kwargs):
98  
-        return insert_query(self.model, values, **kwargs)
10  django/contrib/gis/db/models/query.py
@@ -4,7 +4,7 @@
4 4
 from django.contrib.gis.db.models import aggregates
5 5
 from django.contrib.gis.db.models.fields import get_srid_info, GeometryField, PointField
6 6
 from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery, GeoWhereNode