Django

Code

Changeset 5776

Show
Ignore:
Timestamp:
07/29/07 14:58:24 (1 year ago)
Author:
jbronn
Message:

gis: created backend module, that will (in the future) allow for support of different spatial databases; improved postgis-specific code in preparation for PostGIS 1.2.2 (and beyond).

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/gis/django/contrib/gis/db/backend/postgis/query.py

    r5773 r5776  
    1 # This module is meant to re-define the helper routines used by the 
    2 # django.db.models.query objects to be customized for PostGIS. 
     1""" 
     2  This module contains the spatial lookup types, and the get_geo_where_clause() 
     3  routine for PostGIS. 
     4""" 
    35from django.db import backend 
    4 from django.db.models.query import LOOKUP_SEPARATOR, field_choices, find_field, FieldFound, QUERY_TERMS, get_where_clause 
    5 from django.utils.datastructures import SortedDict 
     6from management import postgis_lib_version 
     7 
     8# Getting the PostGIS version 
     9POSTGIS_VERSION = postgis_lib_version() 
     10MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 = map(int, POSTGIS_VERSION.split('.')) 
     11 
     12# The supported PostGIS versions. 
     13#  TODO: Confirm tests with PostGIS versions 1.1.x -- should work.  Versions <= 1.0.x didn't use GEOS C API. 
     14if MAJOR_VERSION != 1 or MINOR_VERSION1 <= 1: 
     15    raise Exception, 'PostGIS version %s not supported.' % POSTGIS_VERSION 
    616 
    717# PostGIS-specific operators. The commented descriptions of these 
     
    919POSTGIS_OPERATORS = { 
    1020    # The "&<" operator returns true if A's bounding box overlaps or is to the left of B's bounding box. 
    11     'overlaps_left' : '&< %s', 
     21    'overlaps_left' : '&<', 
    1222    # The "&>" operator returns true if A's bounding box overlaps or is to the right of B's bounding box. 
    13     'overlaps_right' : '&> %s', 
     23    'overlaps_right' : '&>', 
    1424    # The "<<" operator returns true if A's bounding box is strictly to the left of B's bounding box. 
    15     'left' : '<< %s', 
     25    'left' : '<<', 
    1626    # The ">>" operator returns true if A's bounding box is strictly to the right of B's bounding box. 
    17     'right' : '>> %s', 
     27    'right' : '>>', 
    1828    # The "&<|" operator returns true if A's bounding box overlaps or is below B's bounding box. 
    19     'overlaps_below' : '&<| %s', 
     29    'overlaps_below' : '&<|', 
    2030    # The "|&>" operator returns true if A's bounding box overlaps or is above B's bounding box. 
    21     'overlaps_above' : '|&> %s', 
     31    'overlaps_above' : '|&>', 
    2232    # The "<<|" operator returns true if A's bounding box is strictly below B's bounding box. 
    23     'strictly_below' : '<<| %s', 
     33    'strictly_below' : '<<|', 
    2434    # The "|>>" operator returns true if A's bounding box is strictly above B's bounding box. 
    25     'strictly_above' : '|>> %s', 
     35    'strictly_above' : '|>>', 
    2636    # The "~=" operator is the "same as" operator. It tests actual geometric equality of two features. So if 
    2737    # A and B are the same feature, vertex-by-vertex, the operator returns true. 
    28     'same_as' : '~= %s', 
    29     'exact' : '~= %s', 
     38    'same_as' : '~=', 
     39    'exact' : '~=', 
    3040    # The "@" operator returns true if A's bounding box is completely contained by B's bounding box. 
    31     'contained' : '@ %s', 
     41    'contained' : '@', 
    3242    # The "~" operator returns true if A's bounding box completely contains B's bounding box. 
    33     'bbcontains' : '~ %s', 
     43    'bbcontains' : '~', 
    3444    # The "&&" operator is the "overlaps" operator. If A's bounding boux overlaps B's bounding box the 
    3545    # operator returns true. 
    36     'bboverlaps' : '&& %s', 
     46    'bboverlaps' : '&&', 
    3747    } 
    3848 
    39 # PostGIS Geometry Functions -- most of these use GEOS. 
     49# PostGIS Geometry Relationship Functions -- most of these use GEOS. 
     50
     51# For PostGIS >= 1.2.2 these routines will do a bounding box query first before calling 
     52#  the more expensive GEOS routines (called 'inline index magic'). 
     53
    4054POSTGIS_GEOMETRY_FUNCTIONS = { 
    41     #'distance' : 'Distance', -- doesn't work right now. 
    42     'equals' : 'Equals', 
    43     'disjoint' : 'Disjoint', 
    44     'touches' : 'Touches', 
    45     'crosses' : 'Crosses', 
    46     'within' : 'Within', 
    47     'overlaps' : 'Overlaps', 
    48     'contains' : 'Contains', 
    49     'intersects' : 'Intersects', 
    50     'relate' : 'Relate', 
     55    'equals' : '%sEquals', 
     56    'disjoint' : '%sDisjoint', 
     57    'touches' : '%sTouches', 
     58    'crosses' : '%sCrosses', 
     59    'within' : '%sWithin', 
     60    'overlaps' : '%sOverlaps', 
     61    'contains' : '%sContains', 
     62    'intersects' : '%sIntersects', 
     63    'relate' : ('%sRelate', str), 
    5164    } 
     65 
     66# Versions of PostGIS >= 1.2.2 changed their naming convention to be 'SQL-MM-centric'. 
     67#  Practically, this means that 'ST_' is appended to geometry function names. 
     68if MINOR_VERSION1 >= 2 and MINOR_VERSION2 >= 2: 
     69    # The ST_DWithin, ST_CoveredBy, and ST_Covers routines become available in 1.2.2. 
     70    POSTGIS_GEOMETRY_FUNCTIONS.update( 
     71        {'dwithin' : ('%sDWithin', float), 
     72         'coveredby' : '%sCoveredBy', 
     73         'covers' : '%sCovers', 
     74         } 
     75        ) 
     76    GEOM_FUNC_PREFIX = 'ST_' 
     77else: 
     78    GEOM_FUNC_PREFIX = '' 
     79 
     80# Updating with the geometry function prefix. 
     81for k, v in POSTGIS_GEOMETRY_FUNCTIONS.items(): 
     82    if isinstance(v, tuple): 
     83        v = list(v) 
     84        v[0] = v[0] % GEOM_FUNC_PREFIX 
     85        v = tuple(v) 
     86    else: 
     87        v = v % GEOM_FUNC_PREFIX 
     88    POSTGIS_GEOMETRY_FUNCTIONS[k] = v 
    5289 
    5390# Any other lookup types that do not require a mapping. 
     
    5996    else: return "'%s'" % value 
    6097 
    61 # These are the PostGIS-customized QUERY_TERMS, combines both the operator
    62 # and the geometry functions. 
     98# These are the PostGIS-customized QUERY_TERMS -- a list of the lookup type
     99# allowed for geographic queries. 
    63100POSTGIS_TERMS = list(POSTGIS_OPERATORS.keys()) # Getting the operators first 
    64101POSTGIS_TERMS += list(POSTGIS_GEOMETRY_FUNCTIONS.keys()) # Adding on the Geometry Functions 
     
    67104 
    68105def get_geo_where_clause(lookup_type, table_prefix, field_name, value): 
     106    "Returns the SQL WHERE clause for use in PostGIS SQL construction." 
    69107    if table_prefix.endswith('.'): 
    70108        table_prefix = backend.quote_name(table_prefix[:-1])+'.' 
     
    73111    # See if a PostGIS operator matches the lookup type first 
    74112    try: 
    75         return '%s%s %s' % (table_prefix, field_name, (POSTGIS_OPERATORS[lookup_type] % '%s')
     113        return '%s%s %s %%s' % (table_prefix, field_name, POSTGIS_OPERATORS[lookup_type]
    76114    except KeyError: 
    77115        pass 
     
    83121        pass 
    84122     
    85     # For any other lookup type 
     123    # Handling 'isnull' lookup type 
    86124    if lookup_type == 'isnull': 
    87125        return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or '')) 
    88126 
    89127    raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type) 
    90  
    91 ####    query.py overloaded functions    #### 
    92 # parse_lookup() and lookup_inner() are modified from their django/db/models/query.py 
    93 #  counterparts to support constructing SQL for geographic queries. 
    94 # 
    95 # Status: Synced with r5609. 
    96 # 
    97 def parse_lookup(kwarg_items, opts): 
    98     # Helper function that handles converting API kwargs 
    99     # (e.g. "name__exact": "tom") to SQL. 
    100     # Returns a tuple of (joins, where, params). 
    101  
    102     # 'joins' is a sorted dictionary describing the tables that must be joined 
    103     # to complete the query. The dictionary is sorted because creation order 
    104     # is significant; it is a dictionary to ensure uniqueness of alias names. 
    105     # 
    106     # Each key-value pair follows the form 
    107     #   alias: (table, join_type, condition) 
    108     # where 
    109     #   alias is the AS alias for the joined table 
    110     #   table is the actual table name to be joined 
    111     #   join_type is the type of join (INNER JOIN, LEFT OUTER JOIN, etc) 
    112     #   condition is the where-like statement over which narrows the join. 
    113     #   alias will be derived from the lookup list name. 
    114     # 
    115     # At present, this method only every returns INNER JOINs; the option is 
    116     # there for others to implement custom Q()s, etc that return other join 
    117     # types. 
    118     joins, where, params = SortedDict(), [], [] 
    119  
    120     for kwarg, value in kwarg_items: 
    121         path = kwarg.split(LOOKUP_SEPARATOR) 
    122         # Extract the last elements of the kwarg. 
    123         # The very-last is the lookup_type (equals, like, etc). 
    124         # The second-last is the table column on which the lookup_type is 
    125         # to be performed. If this name is 'pk', it will be substituted with 
    126         # the name of the primary key. 
    127         # If there is only one part, or the last part is not a query 
    128         # term, assume that the query is an __exact 
    129         lookup_type = path.pop() 
    130         if lookup_type == 'pk': 
    131             lookup_type = 'exact' 
    132             path.append(None) 
    133         elif len(path) == 0 or not ((lookup_type in QUERY_TERMS) or (lookup_type in POSTGIS_TERMS)): 
    134             path.append(lookup_type) 
    135             lookup_type = 'exact' 
    136  
    137         if len(path) < 1: 
    138             raise TypeError, "Cannot parse keyword query %r" % kwarg 
    139  
    140         if value is None: 
    141             # Interpret '__exact=None' as the sql '= NULL'; otherwise, reject 
    142             # all uses of None as a query value. 
    143             if lookup_type != 'exact': 
    144                 raise ValueError, "Cannot use None as a query value" 
    145         elif callable(value): 
    146             value = value() 
    147  
    148         joins2, where2, params2 = lookup_inner(path, lookup_type, value, opts, opts.db_table, None) 
    149         joins.update(joins2) 
    150         where.extend(where2) 
    151         params.extend(params2) 
    152     return joins, where, params 
    153  
    154 def lookup_inner(path, lookup_type, value, opts, table, column): 
    155     qn = backend.quote_name 
    156     joins, where, params = SortedDict(), [], [] 
    157     current_opts = opts 
    158     current_table = table 
    159     current_column = column 
    160     intermediate_table = None 
    161     join_required = False 
    162  
    163     name = path.pop(0) 
    164     # Has the primary key been requested? If so, expand it out 
    165     # to be the name of the current class' primary key 
    166     if name is None or name == 'pk': 
    167         name = current_opts.pk.name 
    168  
    169     # Try to find the name in the fields associated with the current class 
    170     try: 
    171         # Does the name belong to a defined many-to-many field? 
    172         field = find_field(name, current_opts.many_to_many, False) 
    173         if field: 
    174             new_table = current_table + '__' + name 
    175             new_opts = field.rel.to._meta 
    176             new_column = new_opts.pk.column 
    177  
    178             # Need to create an intermediate table join over the m2m table 
    179             # This process hijacks current_table/column to point to the 
    180             # intermediate table. 
    181             current_table = "m2m_" + new_table 
    182             intermediate_table = field.m2m_db_table() 
    183             join_column = field.m2m_reverse_name() 
    184             intermediate_column = field.m2m_column_name() 
    185  
    186             raise FieldFound 
    187  
    188         # Does the name belong to a reverse defined many-to-many field? 
    189         field = find_field(name, current_opts.get_all_related_many_to_many_objects(), True) 
    190         if field: 
    191             new_table = current_table + '__' + name 
    192             new_opts = field.opts 
    193             new_column = new_opts.pk.column 
    194  
    195             # Need to create an intermediate table join over the m2m table. 
    196             # This process hijacks current_table/column to point to the 
    197             # intermediate table. 
    198             current_table = "m2m_" + new_table 
    199             intermediate_table = field.field.m2m_db_table() 
    200             join_column = field.field.m2m_column_name() 
    201             intermediate_column = field.field.m2m_reverse_name() 
    202  
    203             raise FieldFound 
    204  
    205         # Does the name belong to a one-to-many field? 
    206         field = find_field(name, current_opts.get_all_related_objects(), True) 
    207         if field: 
    208             new_table = table + '__' + name 
    209             new_opts = field.opts 
    210             new_column = field.field.column 
    211             join_column = opts.pk.column 
    212  
    213             # 1-N fields MUST be joined, regardless of any other conditions. 
    214             join_required = True 
    215  
    216             raise FieldFound 
    217  
    218         # Does the name belong to a one-to-one, many-to-one, or regular field? 
    219         field = find_field(name, current_opts.fields, False) 
    220         if field: 
    221             if field.rel: # One-to-One/Many-to-one field 
    222                 new_table = current_table + '__' + name 
    223                 new_opts = field.rel.to._meta 
    224                 new_column = new_opts.pk.column 
    225                 join_column = field.column 
    226                 raise FieldFound 
    227             elif path: 
    228                 # For regular fields, if there are still items on the path, 
    229                 # an error has been made. We munge "name" so that the error 
    230                 # properly identifies the cause of the problem. 
    231                 name += LOOKUP_SEPARATOR + path[0] 
    232             else: 
    233                 raise FieldFound 
    234  
    235     except FieldFound: # Match found, loop has been shortcut. 
    236         pass 
    237     else: # No match found. 
    238         choices = field_choices(current_opts.many_to_many, False) + \ 
    239             field_choices(current_opts.get_all_related_many_to_many_objects(), True) + \ 
    240             field_choices(current_opts.get_all_related_objects(), True) + \ 
    241             field_choices(current_opts.fields, False) 
    242         raise TypeError, "Cannot resolve keyword '%s' into field. Choices are: %s" % (name, ", ".join(choices)) 
    243  
    244     # Check whether an intermediate join is required between current_table 
    245     # and new_table. 
    246     if intermediate_table: 
    247         joins[qn(current_table)] = ( 
    248             qn(intermediate_table), "LEFT OUTER JOIN", 
    249             "%s.%s = %s.%s" % (qn(table), qn(current_opts.pk.column), qn(current_table), qn(intermediate_column)) 
    250         ) 
    251  
    252     if path: 
    253         # There are elements left in the path. More joins are required. 
    254         if len(path) == 1 and path[0] in (new_opts.pk.name, None) \ 
    255             and lookup_type in ('exact', 'isnull') and not join_required: 
    256             # If the next and final name query is for a primary key, 
    257             # and the search is for isnull/exact, then the current 
    258             # (for N-1) or intermediate (for N-N) table can be used 
    259             # for the search. No need to join an extra table just 
    260             # to check the primary key. 
    261             new_table = current_table 
    262         else: 
    263             # There are 1 or more name queries pending, and we have ruled out 
    264             # any shortcuts; therefore, a join is required. 
    265             joins[qn(new_table)] = ( 
    266                 qn(new_opts.db_table), "INNER JOIN", 
    267                 "%s.%s = %s.%s" % (qn(current_table), qn(join_column), qn(new_table), qn(new_column)) 
    268             ) 
    269             # If we have made the join, we don't need to tell subsequent 
    270             # recursive calls about the column name we joined on. 
    271             join_column = None 
    272  
    273         # There are name queries remaining. Recurse deeper. 
    274         joins2, where2, params2 = lookup_inner(path, lookup_type, value, new_opts, new_table, join_column) 
    275  
    276         joins.update(joins2) 
    277         where.extend(where2) 
    278         params.extend(params2) 
    279     else: 
    280         # No elements left in path. Current element is the element on which 
    281         # the search is being performed. 
    282  
    283         if join_required: 
    284             # Last query term is a RelatedObject 
    285             if field.field.rel.multiple: 
    286                 # RelatedObject is from a 1-N relation. 
    287                 # Join is required; query operates on joined table. 
    288                 column = new_opts.pk.name 
    289                 joins[qn(new_table)] = ( 
    290                     qn(new_opts.db_table), "INNER JOIN", 
    291                     "%s.%s = %s.%s" % (qn(current_table), qn(join_column), qn(new_table), qn(new_column)) 
    292                 ) 
    293                 current_table = new_table 
    294             else: 
    295                 # RelatedObject is from a 1-1 relation, 
    296                 # No need to join; get the pk value from the related object, 
    297                 # and compare using that. 
    298                 column = current_opts.pk.name 
    299         elif intermediate_table: 
    300             # Last query term is a related object from an N-N relation. 
    301             # Join from intermediate table is sufficient. 
    302             column = join_column 
    303         elif name == current_opts.pk.name and lookup_type in ('exact', 'isnull') and current_column: 
    304             # Last query term is for a primary key. If previous iterations 
    305             # introduced a current/intermediate table that can be used to 
    306             # optimize the query, then use that table and column name. 
    307             column = current_column 
    308         else: 
    309             # Last query term was a normal field. 
    310             column = field.column 
    311  
    312         # If the field is a geometry field, then the WHERE clause will need to be obtained 
    313         # with the get_geo_where_clause() 
    314         if hasattr(field, '_geom'): 
    315             # Getting the geographic where clause. 
    316             gwc = get_geo_where_clause(lookup_type, current_table + '.', column, value) 
    317  
    318             # Getting the geographic parameters from the field. 
    319             geo_params = field.get_db_prep_lookup(lookup_type, value) 
    320       
    321             # If a dictionary was passed back from the field modify the where clause. 
    322             if isinstance(geo_params, dict): 
    323                 gwc = gwc % geo_params['where'] 
    324                 geo_params = geo_params['params'] 
    325             where.append(gwc) 
    326             params.extend(geo_params) 
    327         else: 
    328             where.append(get_where_clause(lookup_type, current_table + '.', column, value)) 
    329             params.extend(field.get_db_prep_lookup(lookup_type, value)) 
    330  
    331     return joins, where, params 
  • django/branches/gis/django/contrib/gis/db/models/fields/__init__.py

    r5773 r5776  
    1 # The Django base Field class. 
    2 from django.db.models.fields import Field 
     1from django.contrib.gis.db.backend import GeoBackendField # depends on the spatial database backend. 
    32from django.contrib.gis.db.models.proxy import GeometryProxy 
    4 from django.contrib.gis.db.models.postgis import POSTGIS_TERMS, quotename 
    53from django.contrib.gis.oldforms import WKTField 
    64from django.utils.functional import curry 
    7 from django.contrib.gis.geos import GEOSGeometry, GEOSException 
    8 from types import StringType 
    95 
    106#TODO: Flesh out widgets. 
    11  
    12 class GeometryField(Field): 
     7class GeometryField(GeoBackendField): 
    138    "The base GIS field -- maps to the OpenGIS Specification Geometry type." 
    149 
     
    3429        super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function 
    3530 
    36     def _add_geom(self, style, db_table): 
    37         """Constructs the addition of the geometry to the table using the 
    38         AddGeometryColumn(...) PostGIS (and OGC standard) stored procedure. 
    39  
    40         Takes the style object (provides syntax highlighting) and the 
    41          database table as parameters. 
    42         """ 
    43         sql = style.SQL_KEYWORD('SELECT ') + \ 
    44               style.SQL_TABLE('AddGeometryColumn') + '(' + \ 
    45               style.SQL_TABLE(quotename(db_table)) + ', ' + \ 
    46               style.SQL_FIELD(quotename(self.column)) + ', ' + \ 
    47               style.SQL_FIELD(str(self._srid)) + ', ' + \ 
    48               style.SQL_COLTYPE(quotename(self._geom)) + ', ' + \ 
    49               style.SQL_KEYWORD(str(self._dim)) + ');' 
    50  
    51         if not self.null: 
    52             # Add a NOT NULL constraint to the field 
    53             sql += '\n' + \ 
    54                    style.SQL_KEYWORD('ALTER TABLE ') + \ 
    55                    style.SQL_TABLE(quotename(db_table, dbl=True)) + \ 
    56                    style.SQL_KEYWORD(' ALTER ') + \ 
    57                    style.SQL_FIELD(quotename(self.column, dbl=True)) + \ 
    58                    style.SQL_KEYWORD(' SET NOT NULL') + ';'  
    59         return sql 
    60  
    61     def _geom_index(self, style, db_table, 
    62                     index_type='GIST', index_opts='GIST_GEOMETRY_OPS'): 
    63         "Creates a GiST index for this geometry field." 
    64         sql = style.SQL_KEYWORD('CREATE INDEX ') + \ 
    65               style.SQL_TABLE(quotename('%s_%s_id' % (db_table, self.column), dbl=True)) + \ 
    66               style.SQL_KEYWORD(' ON ') + \ 
    67               style.SQL_TABLE(quotename(db_table, dbl=True)) + \ 
    68               style.SQL_KEYWORD(' USING ') + \ 
    69               style.SQL_COLTYPE(index_type) + ' ( ' + \ 
    70               style.SQL_FIELD(quotename(self.column, dbl=True)) + ' ' + \ 
    71               style.SQL_KEYWORD(index_opts) + ' );' 
    72         return sql 
    73  
    74     def _post_create_sql(self, style, db_table): 
    75         """Returns SQL that will be executed after the model has been 
    76         created. Geometry columns must be added after creation with the 
    77         PostGIS AddGeometryColumn() function.""" 
    78  
    79         # Getting the AddGeometryColumn() SQL necessary to create a PostGIS 
    80         # geometry field. 
    81         post_sql = self._add_geom(style, db_table) 
    82  
    83         # If the user wants to index this data, then get the indexing SQL as well. 
    84         if self._index: 
    85             return '%s\n%s' % (post_sql, self._geom_index(style, db_table)) 
    86         else: 
    87             return post_sql 
    88  
    89     def _post_delete_sql(self, style, db_table): 
    90         "Drops the geometry column." 
    91         sql = style.SQL_KEYWORD('SELECT ') + \ 
    92             style.SQL_KEYWORD('DropGeometryColumn') + '(' + \ 
    93             style.SQL_TABLE(quotename(db_table)) + ', ' + \ 
    94             style.SQL_FIELD(quotename(self.column)) +  ');' 
    95         return sql 
    96  
    9731    def contribute_to_class(self, cls, name): 
    9832        super(GeometryField, self).contribute_to_class(cls, name) 
     
    11347        return "NoField" 
    11448                                                                 
    115     def get_db_prep_lookup(self, lookup_type, value): 
    116         "Returns field's value prepared for database lookup, accepts WKT and GEOS Geometries for the value." 
    117         if lookup_type in POSTGIS_TERMS: 
    118             if lookup_type == 'isnull': return [value] # special case for NULL geometries. 
    119             if not bool(value): return [None] # If invalid value passed in. 
    120             if isinstance(value, GEOSGeometry): 
    121                 # GEOSGeometry instance passed in. 
    122                 if value.srid != self._srid: 
    123                     # Returning a dictionary instructs the parse_lookup() to add what's in the 'where' key 
    124                     #  to the where parameters, since we need to transform the geometry in the query. 
    125                     return {'where' : "Transform(%s,%s)", 
    126                             'params' : [value, self._srid] 
    127                             } 
    128                 else: 
    129                     # Just return the GEOSGeometry, it has its own psycopg2 adaptor. 
    130                     return [value] 
    131             elif isinstance(value, StringType): 
    132                 # String instance passed in, assuming WKT. 
    133                 # TODO: Any validation needed here to prevent SQL injection? 
    134                 return ["SRID=%d;%s" % (self._srid, value)] 
    135             else: 
    136                 raise TypeError("Invalid type (%s) used for field lookup value." % str(type(value))) 
    137         else: 
    138             raise TypeError("Field has invalid lookup: %s" % lookup_type) 
    139  
    140     def get_db_prep_save(self, value): 
    141         "Prepares the value for saving in the database." 
    142         if not bool(value): return None 
    143         if isinstance(value, GEOSGeometry): 
    144             return value 
    145         else: 
    146             return ("SRID=%d;%s" % (self._srid, wkt)) 
    147  
    148     def get_placeholder(self, value): 
    149         "Provides a proper substitution value for " 
    150         if isinstance(value, GEOSGeometry) and value.srid != self._srid: 
    151             # Adding Transform() to the SQL placeholder. 
    152             return 'Transform(%%s, %s)' % self._srid 
    153         else: 
    154             return '%s' 
    155  
    15649    def get_manipulator_field_objs(self): 
    15750        "Using the WKTField (defined above) to be our manipulator." 
  • django/branches/gis/django/contrib/gis/db/models/query.py

    r5751 r5776  
    22from django.db import backend 
    33from django.contrib.gis.db.models.fields import GeometryField 
    4 from django.contrib.gis.db.models.postgis import parse_lookup 
     4from django.contrib.gis.db.backend import parse_lookup # parse_lookup depends on the spatial database backend. 
    55from django.db.models.fields import FieldDoesNotExist 
    66import operator 
  • django/branches/gis/django/contrib/gis/tests/geoapp/tests.py

    r5773 r5776  
    9292     
    9393    def test005_left_right(self): 
    94         "Testing the left ('<<') right ('>>') operators." 
     94        "Testing the 'left' and 'right' lookup types." 
    9595         
    9696        # Left: A << B => true if xmax(A) < xmin(B) 
  • django/branches/gis/django/contrib/gis/tests/__init__.py

    r5763 r5776  
    11from copy import copy 
    22from unittest import TestSuite, TextTestRunner 
    3 from django.contrib.gis.utils import create_spatial_db 
     3from django.contrib.gis.db.backend import create_spatial_db 
    44from django.db import connection 
    55from django.test.utils import destroy_test_db