Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

gis: spatial-backend enhancements:

 (1) GEOS no longer has psycopg2-specific routines, functionality now part of PostGIS adaptor in the spatial backend.
 (2) ST_GeomFromWKB() now used to enhance performance.
 (3) Moved GeometryProxy back to its original location.
 (4) Should resolve #5498, but not yet confirmed.
 (5) Test-sql files are now backend-specific.


git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6508 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit c049672a74673293e98104c223f034d5f5e08133 1 parent b6c8bba
Justin Bronn authored October 14, 2007
68  django/contrib/gis/db/backend/__init__.py
@@ -11,32 +11,41 @@
11 11
      the backend.
12 12
  (4) The `parse_lookup` function, used for spatial SQL construction by
13 13
      the GeoQuerySet.
14  
- (5) The `create_spatial_db`, `geo_quotename`, and `get_geo_where_clause`
15  
-     routines (needed by `parse_lookup`.
  14
+ (5) The `create_spatial_db`, and `get_geo_where_clause`
  15
+     routines (needed by `parse_lookup`).
16 16
 
17 17
  Currently only PostGIS is supported, but someday backends will be added for
18 18
  additional spatial databases (e.g., Oracle, DB2).
19 19
 """
  20
+from types import StringType, UnicodeType
20 21
 from django.conf import settings
21 22
 from django.db import connection
22 23
 from django.db.models.query import field_choices, find_field, get_where_clause, \
23 24
     FieldFound, LOOKUP_SEPARATOR, QUERY_TERMS
24 25
 from django.utils.datastructures import SortedDict
  26
+from django.contrib.gis.geos import GEOSGeometry
25 27
 
26  
-# These routines default to False
27  
-ASGML, ASKML, UNION = (False, False, False)
  28
+# These routines (needed by GeoManager), default to False.
  29
+ASGML, ASKML, TRANSFORM, UNION= (False, False, False, False)
28 30
 
29 31
 if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
30 32
     # PostGIS is the spatial database, getting the rquired modules, 
31 33
     # renaming as necessary.
32 34
     from django.contrib.gis.db.backend.postgis import \
33 35
         PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \
34  
-        PostGISProxy as GeometryProxy, \
35  
-        create_spatial_db, geo_quotename, get_geo_where_clause, \
36  
-        ASGML, ASKML, UNION
  36
+        create_spatial_db, get_geo_where_clause, gqn, \
  37
+        ASGML, ASKML, GEOM_SELECT, TRANSFORM, UNION
37 38
 else:
38 39
     raise NotImplementedError('No Geographic Backend exists for %s' % settings.DATABASE_NAME)
39 40
 
  41
+def geo_quotename(value):
  42
+    """
  43
+    Returns the quotation used on a given Geometry value using the geometry
  44
+    quoting from the backend (the `gqn` function).
  45
+    """
  46
+    if isinstance(value, (StringType, UnicodeType)): return gqn(value)
  47
+    else: return str(value)
  48
+
40 49
 ####    query.py overloaded functions    ####
41 50
 # parse_lookup() and lookup_inner() are modified from their django/db/models/query.py
42 51
 #  counterparts to support constructing SQL for geographic queries.
@@ -263,38 +272,29 @@ def lookup_inner(path, lookup_type, value, opts, table, column):
263 272
         # If the field is a geometry field, then the WHERE clause will need to be obtained
264 273
         # with the get_geo_where_clause()
265 274
         if hasattr(field, '_geom'):
266  
-            # Do we have multiple arguments, e.g., ST_Relate, ST_DWithin lookup types
267  
-            #  need more than argument.
  275
+            # Do we have multiple arguments, e.g., `relate`, `dwithin` lookup types
  276
+            # need more than argument.
268 277
             multiple_args = isinstance(value, tuple)
269 278
 
270  
-            # Getting the geographic where clause.
271  
-            gwc = get_geo_where_clause(lookup_type, current_table + '.', column, value)
272  
-
273  
-            # Getting the geographic parameters from the field.
  279
+            # Getting the preparation SQL object from the field.
274 280
             if multiple_args:
275  
-                geo_params = field.get_db_prep_lookup(lookup_type, value[0])
  281
+                geo_prep = field.get_db_prep_lookup(lookup_type, value[0])
276 282
             else:
277  
-                geo_params = field.get_db_prep_lookup(lookup_type, value)
278  
-     
279  
-            # If a dictionary was passed back from the field modify the where clause.
280  
-            param_dict = isinstance(geo_params, dict)
281  
-            if param_dict:
282  
-                subst_list = geo_params['where']
283  
-                if multiple_args: subst_list += map(geo_quotename, value[1:])
284  
-                geo_params = geo_params['params']  
285  
-                gwc = gwc % tuple(subst_list)
286  
-            elif multiple_args:
287  
-                # Modify the where clause if we have multiple arguments -- the 
288  
-                #  first substitution will be for another placeholder (for the 
289  
-                #  geometry) since it is already apart of geo_params.
290  
-                subst_list = ['%s']
291  
-                subst_list += map(geo_quotename, value[1:])
292  
-                gwc = gwc % tuple(subst_list)
293  
-                
294  
-            # Finally, appending onto the WHERE clause, and extending with any 
295  
-            #  additional parameters.
  283
+                geo_prep = field.get_db_prep_lookup(lookup_type, value)
  284
+
  285
+            # Getting the adapted geometry from the field.
  286
+            gwc = get_geo_where_clause(lookup_type, current_table + '.', column, value)
  287
+            
  288
+            # A GeoFieldSQL object is returned by `get_db_prep_lookup` -- 
  289
+            # getting the substitution list and the geographic parameters.
  290
+            subst_list = geo_prep.where
  291
+            if multiple_args: subst_list += map(geo_quotename, value[1:])
  292
+            gwc = gwc % tuple(subst_list)
  293
+            
  294
+            # Finally, appending onto the WHERE clause, and extending with
  295
+            # the additional parameters.
296 296
             where.append(gwc)
297  
-            params.extend(geo_params)
  297
+            params.extend(geo_prep.params)
298 298
         else:
299 299
             where.append(get_where_clause(lookup_type, current_table + '.', column, value, db_type))
300 300
             params.extend(field.get_db_prep_lookup(lookup_type, value))
24  django/contrib/gis/db/backend/postgis/__init__.py
... ...
@@ -1,24 +1,10 @@
1 1
 """
2 2
  The PostGIS spatial database backend module.
3 3
 """
4  
-from django.contrib.gis.db.backend.postgis.query import \
5  
-    get_geo_where_clause, geo_quotename, \
6  
-    GEOM_FUNC_PREFIX, POSTGIS_TERMS, \
7  
-    MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2
8 4
 from django.contrib.gis.db.backend.postgis.creation import create_spatial_db
9  
-from django.contrib.gis.db.backend.postgis.field import PostGISField
  5
+from django.contrib.gis.db.backend.postgis.field import PostGISField, gqn
10 6
 from django.contrib.gis.db.backend.postgis.proxy import PostGISProxy
11  
-
12  
-# Functions used by GeoManager methods, and not via lookup types.
13  
-if MAJOR_VERSION == 1:
14  
-    if MINOR_VERSION1 == 3:
15  
-        ASKML = 'ST_AsKML'
16  
-        ASGML = 'ST_AsGML'
17  
-        UNION = 'ST_Union'
18  
-    elif MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 1:
19  
-        ASKML = 'AsKML'
20  
-        ASGML = 'AsGML'
21  
-        UNION = 'GeomUnion'
22  
-        
23  
-
24  
-    
  7
+from django.contrib.gis.db.backend.postgis.query import \
  8
+    get_geo_where_clause, GEOM_FUNC_PREFIX, POSTGIS_TERMS, \
  9
+    MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2, \
  10
+    ASKML, ASGML, GEOM_FROM_TEXT, UNION, TRANSFORM, GEOM_SELECT
29  django/contrib/gis/db/backend/postgis/adaptor.py
... ...
@@ -0,0 +1,29 @@
  1
+"""
  2
+ This object provides quoting for GEOS geometries into PostgreSQL/PostGIS.
  3
+"""
  4
+
  5
+from django.contrib.gis.db.backend.postgis.query import GEOM_FROM_WKB
  6
+from psycopg2 import Binary
  7
+from psycopg2.extensions import ISQLQuote
  8
+
  9
+class PostGISAdaptor(object):
  10
+    def __init__(self, geom, srid):
  11
+        "Initializes on the geometry and the SRID."
  12
+        # Getting the WKB and the SRID
  13
+        self.wkb = geom.wkb
  14
+        self.srid = srid
  15
+
  16
+    def __conform__(self, proto):
  17
+        # Does the given protocol conform to what Psycopg2 expects?
  18
+        if proto == ISQLQuote:
  19
+            return self
  20
+        else:
  21
+            raise Exception('Error implementing psycopg2 protocol. Is psycopg2 installed?')
  22
+
  23
+    def __str__(self):
  24
+        return self.getquoted()
  25
+
  26
+    def getquoted(self):
  27
+        "Returns a properly quoted string for use in PostgreSQL/PostGIS."
  28
+        # Want to use WKB, so wrap with psycopg2 Binary() to quote properly.
  29
+        return "%s(%s, %s)" % (GEOM_FROM_WKB, Binary(self.wkb), self.srid or -1)
94  django/contrib/gis/db/backend/postgis/field.py
... ...
@@ -1,7 +1,17 @@
  1
+from types import StringType, UnicodeType
  2
+from django.db import connection
1 3
 from django.db.models.fields import Field # Django base Field class
2 4
 from django.contrib.gis.geos import GEOSGeometry, GEOSException 
3  
-from django.contrib.gis.db.backend.postgis.query import POSTGIS_TERMS, geo_quotename as quotename
4  
-from types import StringType
  5
+from django.contrib.gis.db.backend.util import GeoFieldSQL
  6
+from django.contrib.gis.db.backend.postgis.adaptor import PostGISAdaptor
  7
+from django.contrib.gis.db.backend.postgis.query import POSTGIS_TERMS, TRANSFORM
  8
+from psycopg2 import Binary
  9
+
  10
+# Quotename & geographic quotename, respectively
  11
+qn = connection.ops.quote_name
  12
+def gqn(value):
  13
+    if isinstance(value, UnicodeType): value = value.encode('ascii')
  14
+    return "'%s'" % value
5 15
 
6 16
 class PostGISField(Field):
7 17
     def _add_geom(self, style, db_table):
@@ -10,23 +20,23 @@ def _add_geom(self, style, db_table):
10 20
         AddGeometryColumn(...) PostGIS (and OGC standard) stored procedure.
11 21
 
12 22
         Takes the style object (provides syntax highlighting) and the
13  
-         database table as parameters.
  23
+        database table as parameters.
14 24
         """
15 25
         sql = style.SQL_KEYWORD('SELECT ') + \
16 26
               style.SQL_TABLE('AddGeometryColumn') + '(' + \
17  
-              style.SQL_TABLE(quotename(db_table)) + ', ' + \
18  
-              style.SQL_FIELD(quotename(self.column)) + ', ' + \
  27
+              style.SQL_TABLE(gqn(db_table)) + ', ' + \
  28
+              style.SQL_FIELD(gqn(self.column)) + ', ' + \
19 29
               style.SQL_FIELD(str(self._srid)) + ', ' + \
20  
-              style.SQL_COLTYPE(quotename(self._geom)) + ', ' + \
  30
+              style.SQL_COLTYPE(gqn(self._geom)) + ', ' + \
21 31
               style.SQL_KEYWORD(str(self._dim)) + ');'
22 32
 
23 33
         if not self.null:
24 34
             # Add a NOT NULL constraint to the field
25 35
             sql += '\n' + \
26 36
                    style.SQL_KEYWORD('ALTER TABLE ') + \
27  
-                   style.SQL_TABLE(quotename(db_table, dbl=True)) + \
  37
+                   style.SQL_TABLE(qn(db_table)) + \
28 38
                    style.SQL_KEYWORD(' ALTER ') + \
29  
-                   style.SQL_FIELD(quotename(self.column, dbl=True)) + \
  39
+                   style.SQL_FIELD(qn(self.column)) + \
30 40
                    style.SQL_KEYWORD(' SET NOT NULL') + ';'
31 41
         return sql
32 42
     
@@ -34,12 +44,12 @@ def _geom_index(self, style, db_table,
34 44
                     index_type='GIST', index_opts='GIST_GEOMETRY_OPS'):
35 45
         "Creates a GiST index for this geometry field."
36 46
         sql = style.SQL_KEYWORD('CREATE INDEX ') + \
37  
-              style.SQL_TABLE(quotename('%s_%s_id' % (db_table, self.column), dbl=True)) + \
  47
+              style.SQL_TABLE(qn('%s_%s_id' % (db_table, self.column))) + \
38 48
               style.SQL_KEYWORD(' ON ') + \
39  
-              style.SQL_TABLE(quotename(db_table, dbl=True)) + \
  49
+              style.SQL_TABLE(qn(db_table)) + \
40 50
               style.SQL_KEYWORD(' USING ') + \
41 51
               style.SQL_COLTYPE(index_type) + ' ( ' + \
42  
-              style.SQL_FIELD(quotename(self.column, dbl=True)) + ' ' + \
  52
+              style.SQL_FIELD(qn(self.column)) + ' ' + \
43 53
               style.SQL_KEYWORD(index_opts) + ' );'
44 54
         return sql
45 55
 
@@ -64,8 +74,8 @@ def _post_delete_sql(self, style, db_table):
64 74
         "Drops the geometry column."
65 75
         sql = style.SQL_KEYWORD('SELECT ') + \
66 76
             style.SQL_KEYWORD('DropGeometryColumn') + '(' + \
67  
-            style.SQL_TABLE(quotename(db_table)) + ', ' + \
68  
-            style.SQL_FIELD(quotename(self.column)) +  ');'
  77
+            style.SQL_TABLE(gqn(db_table)) + ', ' + \
  78
+            style.SQL_FIELD(gqn(self.column)) +  ');'
69 79
         return sql
70 80
 
71 81
     def db_type(self):
@@ -81,26 +91,37 @@ def get_db_prep_lookup(self, lookup_type, value):
81 91
         GEOS Geometries for the value.
82 92
         """
83 93
         if lookup_type in POSTGIS_TERMS:
84  
-            if lookup_type == 'isnull': return [value] # special case for NULL geometries.
85  
-            if not bool(value): return [None] # If invalid value passed in.
  94
+            # special case for isnull lookup
  95
+            if lookup_type == 'isnull':
  96
+                return GeoFieldSQL([], [value])
  97
+
  98
+            # When the input is not a GEOS geometry, attempt to construct one
  99
+            # from the given string input.
86 100
             if isinstance(value, GEOSGeometry):
87  
-                # GEOSGeometry instance passed in.
88  
-                if value.srid != self._srid:
89  
-                    # Returning a dictionary instructs the parse_lookup() to add 
90  
-                    # what's in the 'where' key to the where parameters, since we 
91  
-                    # need to transform the geometry in the query.
92  
-                    return {'where' : ["ST_Transform(%s,%s)"],
93  
-                            'params' : [value, self._srid]
94  
-                            }
95  
-                else:
96  
-                    # Just return the GEOSGeometry, it has its own psycopg2 adaptor.
97  
-                    return [value]
98  
-            elif isinstance(value, StringType):
99  
-                # String instance passed in, assuming WKT.
100  
-                # TODO: Any validation needed here to prevent SQL injection?
101  
-                return ["SRID=%d;%s" % (self._srid, value)]
  101
+                pass
  102
+            elif isinstance(value, (StringType, UnicodeType)):
  103
+                try:
  104
+                    value = GEOSGeometry(value)
  105
+                except GEOSException:
  106
+                    raise TypeError("Could not create geometry from lookup value: %s" % str(value))
  107
+            else:
  108
+                raise TypeError('Cannot use parameter of %s type as lookup parameter.' % type(value))
  109
+
  110
+            # Getting the SRID of the geometry, or defaulting to that of the field if
  111
+            # it is None.
  112
+            if value.srid is None: srid = self._srid
  113
+            else: srid = value.srid
  114
+
  115
+            # The adaptor will be used by psycopg2 for quoting the WKB.
  116
+            adapt = PostGISAdaptor(value, srid)
  117
+
  118
+            if srid != self._srid:
  119
+                # Adding the necessary string substitutions and parameters
  120
+                # to perform a geometry transformation.
  121
+                return GeoFieldSQL(['%s(%%s,%%s)' % TRANSFORM],
  122
+                                       [adapt, self._srid])
102 123
             else:
103  
-                raise TypeError("Invalid type (%s) used for field lookup value." % str(type(value)))
  124
+                return GeoFieldSQL(['%s'], [adapt])
104 125
         else:
105 126
             raise TypeError("Field has invalid lookup: %s" % lookup_type)
106 127
 
@@ -108,24 +129,25 @@ def get_db_prep_save(self, value):
108 129
         "Prepares the value for saving in the database."
109 130
         if not bool(value): return None
110 131
         if isinstance(value, GEOSGeometry):
111  
-            return value
  132
+            return PostGISAdaptor(value, value.srid)
112 133
         else:
113 134
             raise TypeError('Geometry Proxy should only return GEOSGeometry objects.')
114 135
 
115 136
     def get_internal_type(self):
116 137
         """
117  
-        Returns NoField because a stored procedure is used by PostGIS to create the
  138
+        Returns NoField because a stored procedure is used by PostGIS to create
  139
+        the Geometry Fields.
118 140
         """
119 141
         return 'NoField'
120 142
 
121 143
     def get_placeholder(self, value):
122 144
         """
123 145
         Provides a proper substitution value for Geometries that are not in the
124  
-         SRID of the field.  Specifically, this routine will substitute in the
125  
-         ST_Transform() function call.
  146
+        SRID of the field.  Specifically, this routine will substitute in the
  147
+        ST_Transform() function call.
126 148
         """
127 149
         if isinstance(value, GEOSGeometry) and value.srid != self._srid:
128 150
             # Adding Transform() to the SQL placeholder.
129  
-            return 'ST_Transform(%%s, %s)' % self._srid
  151
+            return '%s(%%s, %s)' % (TRANSFORM, self._srid)
130 152
         else:
131 153
             return '%s'
62  django/contrib/gis/db/backend/postgis/query.py
... ...
@@ -1,11 +1,11 @@
1 1
 """
2  
-  This module contains the spatial lookup types, and the get_geo_where_clause()
3  
-  routine for PostGIS.
  2
+ This module contains the spatial lookup types, and the get_geo_where_clause()
  3
+ routine for PostGIS.
4 4
 """
5 5
 from django.db import connection
6 6
 from django.contrib.gis.db.backend.postgis.management import postgis_version_tuple
7 7
 from types import StringType, UnicodeType
8  
-quote_name = connection.ops.quote_name
  8
+qn = connection.ops.quote_name
9 9
 
10 10
 # Getting the PostGIS version information
11 11
 POSTGIS_VERSION, MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 = postgis_version_tuple()
@@ -121,21 +121,16 @@ def get_geom_func(lookup_type):
121 121
 def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
122 122
     "Returns the SQL WHERE clause for use in PostGIS SQL construction."
123 123
     if table_prefix.endswith('.'):
124  
-        table_prefix = quote_name(table_prefix[:-1])+'.'
125  
-    field_name = quote_name(field_name)
  124
+        table_prefix = qn(table_prefix[:-1])+'.'
  125
+    field_name = qn(field_name)
126 126
 
127 127
     # See if a PostGIS operator matches the lookup type first
128  
-    try:
  128
+    if lookup_type in POSTGIS_OPERATORS:
129 129
         return '%s%s %s %%s' % (table_prefix, field_name, POSTGIS_OPERATORS[lookup_type])
130  
-    except KeyError:
131  
-        pass
132 130
 
133 131
     # See if a PostGIS Geometry function matches the lookup type next
134  
-    try:
  132
+    if lookup_type in POSTGIS_GEOMETRY_FUNCTIONS:
135 133
         lookup_info = POSTGIS_GEOMETRY_FUNCTIONS[lookup_type]
136  
-    except KeyError:
137  
-        pass
138  
-    else:
139 134
         # Lookup types that are tuples take tuple arguments, e.g., 'relate' and 
140 135
         #  'dwithin' lookup types.
141 136
         if isinstance(lookup_info, tuple):
@@ -145,7 +140,7 @@ def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
145 140
 
146 141
             # Ensuring that a tuple _value_ was passed in from the user
147 142
             if not isinstance(value, tuple) or len(value) != 2: 
148  
-                raise TypeError('2-element tuple required for %s lookup type.' % lookup_type)
  143
+                raise TypeError('2-element tuple required for `%s` lookup type.' % lookup_type)
149 144
             
150 145
             # Ensuring the argument type matches what we expect.
151 146
             if not isinstance(value[1], arg_type):
@@ -154,7 +149,7 @@ def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
154 149
             return "%s(%s%s, %%s, %%s)" % (func, table_prefix, field_name)
155 150
         else:
156 151
             # Returning the SQL necessary for the geometry function call. For example: 
157  
-            #  ST_Contains("geoapp_country"."poly", ST_GeomFromText(..))
  152
+            #  ST_Contains("geoapp_country"."poly", ST_GeomFromWKB(..))
158 153
             return '%s(%s%s, %%s)' % (lookup_info, table_prefix, field_name)
159 154
     
160 155
     # Handling 'isnull' lookup type
@@ -163,10 +158,35 @@ def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
163 158
 
164 159
     raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
165 160
 
166  
-def geo_quotename(value, dbl=False):
167  
-    "Returns the quotation used for PostGIS on a given value (uses single quotes by default)."
168  
-    if isinstance(value, (StringType, UnicodeType)):
169  
-        if dbl: return '"%s"' % value
170  
-        else: return "'%s'" % value
171  
-    else:
172  
-        return str(value)
  161
+# Functions that we define manually.
  162
+if MAJOR_VERSION == 1:
  163
+    if MINOR_VERSION1 == 3:
  164
+        # PostGIS versions 1.3.x
  165
+        ASKML = 'ST_AsKML'
  166
+        ASGML = 'ST_AsGML'
  167
+        GEOM_FROM_TEXT = 'ST_GeomFromText'
  168
+        GEOM_FROM_WKB = 'ST_GeomFromWKB'
  169
+        UNION = 'ST_Union'
  170
+        TRANSFORM = 'ST_Transform'
  171
+    elif MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 1:
  172
+        # PostGIS versions 1.2.x
  173
+        ASKML = 'AsKML'
  174
+        ASGML = 'AsGML'
  175
+        GEOM_FROM_TEXT = 'GeomFromText'
  176
+        GEOM_FROM_WKB = 'GeomFromWKB'
  177
+        UNION = 'GeomUnion'
  178
+        TRANSFORM = 'Transform'
  179
+    elif MINOR_VERSION1 == 1 and MINOR_VERSION2 >= 0:
  180
+        # PostGIS versions 1.1.x
  181
+        ASKML = False
  182
+        ASGML = 'AsGML'
  183
+        GEOM_FROM_TEXT = 'GeomFromText'
  184
+        GEOM_FROM_WKB = 'GeomFromWKB'
  185
+        TRANSFORM = 'Transform'
  186
+        UNION = 'GeomUnion'
  187
+
  188
+# Custom selection not needed for PostGIS since GEOS geometries may be
  189
+# instantiated directly from the HEXEWKB returned by default.  If
  190
+# WKT is needed for some reason in the future, this value may be changed,
  191
+# 'AsText(%s)'
  192
+GEOM_SELECT = None
8  django/contrib/gis/db/backend/util.py
... ...
@@ -0,0 +1,8 @@
  1
+class GeoFieldSQL(object):
  2
+    """
  3
+    Container for passing values to `parse_lookup` from the various
  4
+    backend geometry fields.
  5
+    """
  6
+    def __init__(self, where=[], params=[]):
  7
+        self.where = where
  8
+        self.params = params
2  django/contrib/gis/db/models/__init__.py
@@ -7,7 +7,7 @@
7 7
 # The GeoQ object
8 8
 from django.contrib.gis.db.models.query import GeoQ
9 9
 
10  
-# The various PostGIS/OpenGIS enabled fields.
  10
+# The geographic-enabled fields.
11 11
 from django.contrib.gis.db.models.fields import \
12 12
      GeometryField, PointField, LineStringField, PolygonField, \
13 13
      MultiPointField, MultiLineStringField, MultiPolygonField, \
3  django/contrib/gis/db/models/fields/__init__.py
... ...
@@ -1,5 +1,6 @@
1 1
 from django.conf import settings
2  
-from django.contrib.gis.db.backend import GeoBackendField, GeometryProxy # these depend on the spatial database backend.
  2
+from django.contrib.gis.db.backend import GeoBackendField # these depend on the spatial database backend.
  3
+from django.contrib.gis.db.models.proxy import GeometryProxy
3 4
 from django.contrib.gis.oldforms import WKTField
4 5
 from django.contrib.gis.geos import GEOSGeometry
5 6
 
2  django/contrib/gis/db/backend/postgis/proxy.py → django/contrib/gis/db/models/proxy.py
@@ -8,7 +8,7 @@
8 8
 
9 9
 from types import NoneType, StringType, UnicodeType
10 10
 
11  
-class PostGISProxy(object): 
  11
+class GeometryProxy(object): 
12 12
     def __init__(self, klass, field): 
13 13
         """
14 14
         Proxy initializes on the given Geometry class (not an instance) and 
33  django/contrib/gis/db/models/query.py
@@ -6,7 +6,7 @@
6 6
 from django.utils.datastructures import SortedDict
7 7
 from django.contrib.gis.db.models.fields import GeometryField
8 8
 # parse_lookup depends on the spatial database backend.
9  
-from django.contrib.gis.db.backend import parse_lookup, ASGML, ASKML, UNION
  9
+from django.contrib.gis.db.backend import parse_lookup, ASGML, ASKML, GEOM_SELECT, TRANSFORM, UNION
10 10
 from django.contrib.gis.geos import GEOSGeometry
11 11
 
12 12
 class GeoQ(Q):
@@ -29,6 +29,11 @@ def __init__(self, model=None):
29 29
         # For replacement fields in the SELECT.
30 30
         self._custom_select = {}
31 31
 
  32
+        # If GEOM_SELECT is defined in the backend, then it will be used
  33
+        # for the selection format of the geometry column.
  34
+        if GEOM_SELECT: self._geo_fmt = GEOM_SELECT
  35
+        else: self._geo_fmt = '%s'
  36
+
32 37
     def _filter_or_exclude(self, mapper, *args, **kwargs):
33 38
         # mapper is a callable used to transform Q objects,
34 39
         # or None for identity transform
@@ -57,10 +62,21 @@ def _get_sql_clause(self):
57 62
         #  GeoQuerySet. Specifically, this allows operations to be done on fields 
58 63
         #  in the SELECT, overriding their values -- this is different from using 
59 64
         #  QuerySet.extra(select=foo) because extra() adds an  an _additional_ 
60  
-        #  field to be selected.  Used in returning transformed geometries.
  65
+        #  field to be selected.  Used in returning transformed geometries, and
  66
+        #  handling the selection of native database geometry formats.
61 67
         for f in opts.fields:
62  
-            if f.column in self._custom_select: select.append(self._custom_select[f.column])
63  
-            else: select.append(self._field_column(f))
  68
+            # Getting the selection format string.
  69
+            if hasattr(f, '_geom'): sel_fmt = self._geo_fmt
  70
+            else: sel_fmt = '%s'
  71
+                
  72
+            # Getting the field selection substitution string
  73
+            if f.column in self._custom_select:
  74
+                fld_sel = self._custom_select[f.column]
  75
+            else:
  76
+                fld_sel = self._field_column(f)
  77
+
  78
+            # Appending the selection 
  79
+            select.append(sel_fmt % fld_sel)
64 80
 
65 81
         tables = [quote_only_if_word(t) for t in self._tables]
66 82
         joins = SortedDict()
@@ -204,13 +220,16 @@ def transform(self, field_name, srid=4326):
204 220
         # Is the given field name a geographic field?
205 221
         field = self.model._meta.get_field(field_name)
206 222
         if not isinstance(field, GeometryField):
207  
-            raise TypeError('ST_Transform() only available for GeometryFields')
  223
+            raise TypeError('%s() only available for GeometryFields' % TRANSFORM)
  224
+
  225
+        # If there's already custom select SQL.
  226
+        col = self._custom_select.get(field.column, self._field_column(field))
208 227
 
209 228
         # Setting the key for the field's column with the custom SELECT SQL to 
210 229
         #  override the geometry column returned from the database.
211 230
         self._custom_select[field.column] = \
212  
-            '(ST_Transform(%s, %s)) AS %s' % (self._field_column(field), srid, 
213  
-                                              connection.ops.quote_name(field.column))
  231
+            '(%s(%s, %s)) AS %s' % (TRANSFORM, col, srid,
  232
+                                    connection.ops.quote_name(field.column))
214 233
         return self._clone()
215 234
 
216 235
     def union(self, field_name):
32  django/contrib/gis/geos/base.py
@@ -12,7 +12,7 @@
12 12
 import re
13 13
 from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs
14 14
 from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
15  
-from django.contrib.gis.geos.libgeos import lgeos, HAS_NUMPY, ISQLQuote
  15
+from django.contrib.gis.geos.libgeos import lgeos, HAS_NUMPY
16 16
 from django.contrib.gis.geos.pointer import GEOSPointer, NULL_GEOM
17 17
 
18 18
 # Trying to import GDAL libraries, if available.  Have to place in
@@ -47,14 +47,15 @@ def __init__(self, geo_input, srid=None):
47 47
         
48 48
         The `srid` keyword is used to specify the Source Reference Identifier
49 49
          (SRID) number for this Geometry.  If not set, the SRID will be None.
50  
-        """
51  
-
  50
+        """ 
  51
+        from_hex = False
52 52
         if isinstance(geo_input, UnicodeType):
53 53
             # Encoding to ASCII, WKT or HEXEWKB doesn't need any more.
54 54
             geo_input = geo_input.encode('ascii')
55 55
         if isinstance(geo_input, StringType):
56 56
             if hex_regex.match(geo_input):
57 57
                 # If the regex matches, the geometry is in HEX form.
  58
+                from_hex = True
58 59
                 sz = c_size_t(len(geo_input))
59 60
                 buf = create_string_buffer(geo_input)
60 61
                 g = lgeos.GEOSGeomFromHEX_buf(buf, sz)
@@ -62,7 +63,7 @@ def __init__(self, geo_input, srid=None):
62 63
                 # Otherwise, the geometry is in WKT form.
63 64
                 g = lgeos.GEOSGeomFromWKT(c_char_p(geo_input))
64 65
             else:
65  
-                raise GEOSException, 'given string input "%s" unrecognized as WKT or HEXEWKB.' % geo_input
  66
+                raise GEOSException('String or unicode input unrecognized as WKT or HEXEWKB.')
66 67
         elif isinstance(geo_input, (IntType, GEOSPointer)):
67 68
             # When the input is either a memory address (an integer), or a 
68 69
             #  GEOSPointer object.
@@ -85,6 +86,10 @@ def __init__(self, geo_input, srid=None):
85 86
         # Setting the SRID, if given.
86 87
         if srid and isinstance(srid, int): self.srid = srid
87 88
 
  89
+        # Exported HEX from other GEOS geometries will have -1 SRID -- 
  90
+        # set here to 0, when the SRID is not explicitly given.
  91
+        if not srid and from_hex: self.srid = 0
  92
+
88 93
         # Setting the class type (e.g., 'Point', 'Polygon', etc.)
89 94
         self.__class__ = GEOS_CLASSES[self.geom_type]
90 95
 
@@ -207,19 +212,6 @@ def _reassign(self, new_geom):
207 212
         self.__class__ = GEOS_CLASSES[gtype]
208 213
         if isinstance(self, (Polygon, GeometryCollection)): self._populate()
209 214
 
210  
-    #### Psycopg2 database adaptor routines ####
211  
-    def __conform__(self, proto):
212  
-        # Does the given protocol conform to what Psycopg2 expects?
213  
-        if proto == ISQLQuote: 
214  
-            return self
215  
-        else:
216  
-            raise GEOSException, 'Error implementing psycopg2 protocol. Is psycopg2 installed?'
217  
-
218  
-    def getquoted(self):
219  
-        "Returns a properly quoted string for use in PostgreSQL/PostGIS."
220  
-        # Using ST_GeomFromText(), corresponds to SQL/MM ISO standard.
221  
-        return "ST_GeomFromText('%s', %s)" % (self.wkt, self.srid or -1)
222  
-    
223 215
     #### Coordinate Sequence Routines ####
224 216
     @property
225 217
     def has_cs(self):
@@ -425,7 +417,11 @@ def wkt(self):
425 417
 
426 418
     @property
427 419
     def hex(self):
428  
-        "Returns the HEXEWKB of the Geometry."
  420
+        """
  421
+        Returns the HEX of the Geometry -- please note that the SRID is not
  422
+        included in this representation, because the GEOS C library uses
  423
+        -1 by default, even if the SRID is set.
  424
+        """
429 425
         sz = c_size_t()
430 426
         h = lgeos.GEOSGeomToHEX_buf(self._ptr(), byref(sz))
431 427
         return string_at(h, sz.value)
6  django/contrib/gis/geos/libgeos.py
@@ -18,12 +18,6 @@
18 18
 except ImportError:
19 19
     HAS_NUMPY = False
20 20
 
21  
-# Is psycopg2 available?
22  
-try:
23  
-    from psycopg2.extensions import ISQLQuote
24  
-except (ImportError, EnvironmentError):
25  
-    ISQLQuote = None
26  
-
27 21
 # Setting the appropriate name for the GEOS-C library, depending on which
28 22
 # OS and POSIX platform we're running.
29 23
 if os.name == 'nt':
0  django/contrib/gis/tests/geoapp/sql/city.sql → ...gis/tests/geoapp/sql/city.postgresql_psycopg2.sql
File renamed without changes
0  django/contrib/gis/tests/geoapp/sql/country.sql → .../tests/geoapp/sql/country.postgresql_psycopg2.sql
File renamed without changes
0  django/contrib/gis/tests/geoapp/sql/state.sql → ...is/tests/geoapp/sql/state.postgresql_psycopg2.sql
File renamed without changes

0 notes on commit c049672

Please sign in to comment.
Something went wrong with that request. Please try again.