web/lib/django/contrib/gis/db/models/sql/where.py
author ymh <ymh.work@gmail.com>
Wed, 02 Jun 2010 18:57:35 +0200
changeset 38 77b6da96e6f1
permissions -rw-r--r--
update django
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
38
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     1
from django.db.models.fields import Field, FieldDoesNotExist
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     2
from django.db.models.sql.constants import LOOKUP_SEP
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     3
from django.db.models.sql.expressions import SQLEvaluator
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     4
from django.db.models.sql.where import Constraint, WhereNode
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     5
from django.contrib.gis.db.models.fields import GeometryField
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     6
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     7
class GeoConstraint(Constraint):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     8
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     9
    This subclass overrides `process` to better handle geographic SQL
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    10
    construction.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    11
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    12
    def __init__(self, init_constraint):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    13
        self.alias = init_constraint.alias
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    14
        self.col = init_constraint.col
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    15
        self.field = init_constraint.field
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    16
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    17
    def process(self, lookup_type, value, connection):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    18
        if isinstance(value, SQLEvaluator):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    19
            # Make sure the F Expression destination field exists, and
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    20
            # set an `srid` attribute with the same as that of the
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    21
            # destination.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    22
            geo_fld = GeoWhereNode._check_geo_field(value.opts, value.expression.name)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    23
            if not geo_fld:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    24
                raise ValueError('No geographic field found in expression.')
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    25
            value.srid = geo_fld.srid
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    26
        db_type = self.field.db_type(connection=connection)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    27
        params = self.field.get_db_prep_lookup(lookup_type, value, connection=connection)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    28
        return (self.alias, self.col, db_type), params
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    29
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    30
class GeoWhereNode(WhereNode):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    31
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    32
    Used to represent the SQL where-clause for spatial databases --
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    33
    these are tied to the GeoQuery class that created it.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    34
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    35
    def add(self, data, connector):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    36
        if isinstance(data, (list, tuple)):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    37
            obj, lookup_type, value = data
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    38
            if ( isinstance(obj, Constraint) and
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    39
                 isinstance(obj.field, GeometryField) ):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    40
                data = (GeoConstraint(obj), lookup_type, value)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    41
        super(GeoWhereNode, self).add(data, connector)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    42
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    43
    def make_atom(self, child, qn, connection):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    44
        lvalue, lookup_type, value_annot, params_or_value = child
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    45
        if isinstance(lvalue, GeoConstraint):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    46
            data, params = lvalue.process(lookup_type, params_or_value, connection)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    47
            spatial_sql = connection.ops.spatial_lookup_sql(data, lookup_type, params_or_value, lvalue.field, qn)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    48
            return spatial_sql, params
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    49
        else:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    50
            return super(GeoWhereNode, self).make_atom(child, qn, connection)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    51
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    52
    @classmethod
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    53
    def _check_geo_field(cls, opts, lookup):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    54
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    55
        Utility for checking the given lookup with the given model options.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    56
        The lookup is a string either specifying the geographic field, e.g.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    57
        'point, 'the_geom', or a related lookup on a geographic field like
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    58
        'address__point'.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    59
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    60
        If a GeometryField exists according to the given lookup on the model
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    61
        options, it will be returned.  Otherwise returns None.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    62
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    63
        # This takes into account the situation where the lookup is a
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    64
        # lookup to a related geographic field, e.g., 'address__point'.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    65
        field_list = lookup.split(LOOKUP_SEP)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    66
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    67
        # Reversing so list operates like a queue of related lookups,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    68
        # and popping the top lookup.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    69
        field_list.reverse()
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    70
        fld_name = field_list.pop()
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    71
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    72
        try:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    73
            geo_fld = opts.get_field(fld_name)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    74
            # If the field list is still around, then it means that the
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    75
            # lookup was for a geometry field across a relationship --
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    76
            # thus we keep on getting the related model options and the
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    77
            # model field associated with the next field in the list
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    78
            # until there's no more left.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    79
            while len(field_list):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    80
                opts = geo_fld.rel.to._meta
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    81
                geo_fld = opts.get_field(field_list.pop())
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    82
        except (FieldDoesNotExist, AttributeError):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    83
            return False
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    84
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    85
        # Finally, make sure we got a Geographic field and return.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    86
        if isinstance(geo_fld, GeometryField):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    87
            return geo_fld
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    88
        else:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    89
            return False