Skip to content

Commit b6c8bba

Browse files
committed
gis: spatial-database compatibility and usability changes:
(1) The SpatialRefSys and GeometryColumns models and GeometryProxy have been moved to the PostGIS backend because their functionality depends on the spatial databse. (2) The GeoMixin is no longer required, as all the functionality contributed by the extra instance methods has been moved to the GeometryProxy. (3) The `_post_create_sql` field now returns a tuple of SQL statements, instead of a string. git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6467 bcc190cf-cafb-0310-a4f2-bffc1f526a37
1 parent 5d7fd63 commit b6c8bba

File tree

12 files changed

+253
-201
lines changed

12 files changed

+253
-201
lines changed

django/contrib/gis/db/backend/__init__.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
"""
2-
This module provides the backend for spatial SQL construction with Django.
3-
4-
Specifically, this module will import the correct routines and modules
5-
needed for GeoDjango
6-
7-
(1) GeoBackEndField, a base class needed for GeometryField.
8-
(2) The parse_lookup() function, used for spatial SQL construction by
9-
the GeoQuerySet.
10-
11-
Currently only PostGIS is supported, but someday backends will be added for
12-
additional spatial databases (e.g., Oracle, DB2).
2+
This module provides the backend for spatial SQL construction with Django.
3+
4+
Specifically, this module will import the correct routines and modules
5+
needed for GeoDjango.
6+
7+
(1) GeoBackEndField, a base class needed for GeometryField.
8+
(2) GeometryProxy, for lazy-instantiated geometries from the
9+
database output.
10+
(3) GIS_TERMS, a list of acceptable geographic lookup types for
11+
the backend.
12+
(4) The `parse_lookup` function, used for spatial SQL construction by
13+
the GeoQuerySet.
14+
(5) The `create_spatial_db`, `geo_quotename`, and `get_geo_where_clause`
15+
routines (needed by `parse_lookup`.
16+
17+
Currently only PostGIS is supported, but someday backends will be added for
18+
additional spatial databases (e.g., Oracle, DB2).
1319
"""
1420
from django.conf import settings
1521
from django.db import connection
@@ -21,9 +27,11 @@
2127
ASGML, ASKML, UNION = (False, False, False)
2228

2329
if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
24-
# PostGIS is the spatial database, getting the rquired modules, renaming as necessary.
30+
# PostGIS is the spatial database, getting the rquired modules,
31+
# renaming as necessary.
2532
from django.contrib.gis.db.backend.postgis import \
2633
PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \
34+
PostGISProxy as GeometryProxy, \
2735
create_spatial_db, geo_quotename, get_geo_where_clause, \
2836
ASGML, ASKML, UNION
2937
else:

django/contrib/gis/db/backend/postgis/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2
88
from django.contrib.gis.db.backend.postgis.creation import create_spatial_db
99
from django.contrib.gis.db.backend.postgis.field import PostGISField
10+
from django.contrib.gis.db.backend.postgis.proxy import PostGISProxy
1011

1112
# Functions used by GeoManager methods, and not via lookup types.
1213
if MAJOR_VERSION == 1:

django/contrib/gis/db/backend/postgis/field.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,21 @@ def _geom_index(self, style, db_table,
4444
return sql
4545

4646
def _post_create_sql(self, style, db_table):
47-
"""Returns SQL that will be executed after the model has been
47+
"""
48+
Returns SQL that will be executed after the model has been
4849
created. Geometry columns must be added after creation with the
49-
PostGIS AddGeometryColumn() function."""
50+
PostGIS AddGeometryColumn() function.
51+
"""
5052

5153
# Getting the AddGeometryColumn() SQL necessary to create a PostGIS
5254
# geometry field.
5355
post_sql = self._add_geom(style, db_table)
5456

5557
# If the user wants to index this data, then get the indexing SQL as well.
5658
if self._index:
57-
return '%s\n%s' % (post_sql, self._geom_index(style, db_table))
59+
return (post_sql, self._geom_index(style, db_table))
5860
else:
59-
return post_sql
61+
return (post_sql,)
6062

6163
def _post_delete_sql(self, style, db_table):
6264
"Drops the geometry column."
@@ -66,9 +68,18 @@ def _post_delete_sql(self, style, db_table):
6668
style.SQL_FIELD(quotename(self.column)) + ');'
6769
return sql
6870

71+
def db_type(self):
72+
"""
73+
PostGIS geometry columns are added by stored procedures, should be
74+
None.
75+
"""
76+
return None
77+
6978
def get_db_prep_lookup(self, lookup_type, value):
70-
"""Returns field's value prepared for database lookup, accepts WKT and
71-
GEOS Geometries for the value."""
79+
"""
80+
Returns field's value prepared for database lookup, accepts WKT and
81+
GEOS Geometries for the value.
82+
"""
7283
if lookup_type in POSTGIS_TERMS:
7384
if lookup_type == 'isnull': return [value] # special case for NULL geometries.
7485
if not bool(value): return [None] # If invalid value passed in.
@@ -101,6 +112,12 @@ def get_db_prep_save(self, value):
101112
else:
102113
raise TypeError('Geometry Proxy should only return GEOSGeometry objects.')
103114

115+
def get_internal_type(self):
116+
"""
117+
Returns NoField because a stored procedure is used by PostGIS to create the
118+
"""
119+
return 'NoField'
120+
104121
def get_placeholder(self, value):
105122
"""
106123
Provides a proper substitution value for Geometries that are not in the
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""
2+
The GeometryColumns and SpatialRefSys models for the PostGIS backend.
3+
"""
4+
from django.db import models
5+
from django.contrib.gis.models import SpatialRefSysMixin
6+
7+
# Checking for the presence of GDAL (needed for the SpatialReference object)
8+
from django.contrib.gis.gdal import HAS_GDAL
9+
if HAS_GDAL:
10+
from django.contrib.gis.gdal import SpatialReference
11+
12+
class GeometryColumns(models.Model):
13+
"""
14+
The 'geometry_columns' table from the PostGIS. See the PostGIS
15+
documentation at Ch. 4.2.2.
16+
"""
17+
f_table_catalog = models.CharField(maxlength=256)
18+
f_table_schema = models.CharField(maxlength=256)
19+
f_table_name = models.CharField(maxlength=256, primary_key=True)
20+
f_geometry_column = models.CharField(maxlength=256)
21+
coord_dimension = models.IntegerField()
22+
srid = models.IntegerField()
23+
type = models.CharField(maxlength=30)
24+
25+
class Meta:
26+
db_table = 'geometry_columns'
27+
28+
@classmethod
29+
def table_name(self):
30+
"Class method for returning the table name field for this model."
31+
return 'f_table_name'
32+
33+
def __unicode__(self):
34+
return "%s.%s - %dD %s field (SRID: %d)" % \
35+
(self.f_table_name, self.f_geometry_column,
36+
self.coord_dimension, self.type, self.srid)
37+
38+
class SpatialRefSys(models.Model, SpatialRefSysMixin):
39+
"""
40+
The 'spatial_ref_sys' table from PostGIS. See the PostGIS
41+
documentaiton at Ch. 4.2.1.
42+
"""
43+
srid = models.IntegerField(primary_key=True)
44+
auth_name = models.CharField(maxlength=256)
45+
auth_srid = models.IntegerField()
46+
srtext = models.CharField(maxlength=2048)
47+
proj4text = models.CharField(maxlength=2048)
48+
49+
class Meta:
50+
db_table = 'spatial_ref_sys'
51+
52+
@property
53+
def wkt(self):
54+
return self.srtext
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""
2+
The GeometryProxy object, allows for lazy-geometries. The proxy uses
3+
Python descriptors for instantiating and setting GEOS Geometry objects
4+
corresponding to geographic model fields.
5+
6+
Thanks to Robert Coup for providing this functionality (see #4322).
7+
"""
8+
9+
from types import NoneType, StringType, UnicodeType
10+
11+
class PostGISProxy(object):
12+
def __init__(self, klass, field):
13+
"""
14+
Proxy initializes on the given Geometry class (not an instance) and
15+
the GeometryField.
16+
"""
17+
self._field = field
18+
self._klass = klass
19+
20+
def __get__(self, obj, type=None):
21+
"""
22+
This accessor retrieves the geometry, initializing it using the geometry
23+
class specified during initialization and the HEXEWKB value of the field.
24+
Currently, only GEOS or OGR geometries are supported.
25+
"""
26+
# Getting the value of the field.
27+
geom_value = obj.__dict__[self._field.attname]
28+
29+
if isinstance(geom_value, self._klass):
30+
geom = geom_value
31+
elif (geom_value is None) or (geom_value==''):
32+
geom = None
33+
else:
34+
# Otherwise, a GEOSGeometry object is built using the field's contents,
35+
# and the model's corresponding attribute is set.
36+
geom = self._klass(geom_value)
37+
setattr(obj, self._field.attname, geom)
38+
return geom
39+
40+
def __set__(self, obj, value):
41+
"""
42+
This accessor sets the proxied geometry with the geometry class
43+
specified during initialization. Values of None, HEXEWKB, or WKT may
44+
be used to set the geometry as well.
45+
"""
46+
# The OGC Geometry type of the field.
47+
gtype = self._field._geom
48+
49+
# The geometry type must match that of the field -- unless the
50+
# general GeometryField is used.
51+
if isinstance(value, self._klass) and (str(value.geom_type).upper() == gtype or gtype == 'GEOMETRY'):
52+
# Assigning the SRID to the geometry.
53+
if value.srid is None: value.srid = self._field._srid
54+
elif isinstance(value, (NoneType, StringType, UnicodeType)):
55+
# Set with None, WKT, or HEX
56+
pass
57+
else:
58+
raise TypeError('cannot set %s GeometryProxy with value of type: %s' % (obj.__class__.__name__, type(value)))
59+
60+
# Setting the objects dictionary with the value, and returning.
61+
obj.__dict__[self._field.attname] = value
62+
return value

django/contrib/gis/db/models/fields/__init__.py

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
from django.contrib.gis.db.backend import GeoBackendField # depends on the spatial database backend.
2-
from django.contrib.gis.db.models.proxy import GeometryProxy
1+
from django.conf import settings
2+
from django.contrib.gis.db.backend import GeoBackendField, GeometryProxy # these depend on the spatial database backend.
33
from django.contrib.gis.oldforms import WKTField
4-
from django.utils.functional import curry
4+
from django.contrib.gis.geos import GEOSGeometry
55

6-
#TODO: Flesh out widgets.
6+
#TODO: Flesh out widgets; consider adding support for OGR Geometry proxies.
77
class GeometryField(GeoBackendField):
88
"The base GIS field -- maps to the OpenGIS Specification Geometry type."
99

@@ -31,26 +31,9 @@ def __init__(self, srid=4326, index=True, dim=2, **kwargs):
3131
def contribute_to_class(self, cls, name):
3232
super(GeometryField, self).contribute_to_class(cls, name)
3333

34-
# setup for lazy-instantiated GEOSGeometry objects
35-
setattr(cls, self.attname, GeometryProxy(self))
36-
37-
# Adding needed accessor functions
38-
setattr(cls, 'get_%s_geos' % self.name, curry(cls._get_GEOM_geos, field=self))
39-
setattr(cls, 'get_%s_ogr' % self.name, curry(cls._get_GEOM_ogr, field=self, srid=self._srid))
40-
setattr(cls, 'get_%s_srid' % self.name, curry(cls._get_GEOM_srid, srid=self._srid))
41-
setattr(cls, 'get_%s_srs' % self.name, curry(cls._get_GEOM_srs, srid=self._srid))
42-
setattr(cls, 'get_%s_wkt' % self.name, curry(cls._get_GEOM_wkt, field=self))
43-
setattr(cls, 'get_%s_centroid' % self.name, curry(cls._get_GEOM_centroid, field=self))
44-
setattr(cls, 'get_%s_area' % self.name, curry(cls._get_GEOM_area, field=self))
34+
# Setup for lazy-instantiated GEOSGeometry object.
35+
setattr(cls, self.attname, GeometryProxy(GEOSGeometry, self))
4536

46-
def get_internal_type(self):
47-
return "NoField"
48-
49-
def db_type(self):
50-
# Geometry columns are added by stored procedures, and thus should
51-
# be None.
52-
return None
53-
5437
def get_manipulator_field_objs(self):
5538
"Using the WKTField (defined above) to be our manipulator."
5639
return [WKTField]
Lines changed: 8 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,11 @@
1-
from warnings import warn
2-
3-
# GEOS is a requirement, GDAL is not
4-
from django.contrib.gis.geos import GEOSGeometry
5-
from django.contrib.gis.gdal import HAS_GDAL
6-
if HAS_GDAL:
7-
from django.contrib.gis.gdal import OGRGeometry, SpatialReference
8-
91
# Until model subclassing is a possibility, a mixin class is used to add
102
# the necessary functions that may be contributed for geographic objects.
113
class GeoMixin:
12-
"The Geographic Mixin class provides routines for geographic objects."
13-
14-
# A subclass of Model is specifically needed so that these geographic
15-
# routines are present for instantiations of the models.
16-
def _get_GEOM_geos(self, field):
17-
"Returns a GEOS Python object for the geometry."
18-
warn("use model.%s" % field.attname, DeprecationWarning)
19-
return getattr(self, field.attname)
20-
21-
def _get_GEOM_ogr(self, field, srid):
22-
"Returns an OGR Python object for the geometry."
23-
if HAS_GDAL:
24-
return OGRGeometry(getattr(self, field.attname).wkt,
25-
SpatialReference('EPSG:%d' % srid))
26-
else:
27-
raise Exception, "GDAL is not installed!"
28-
29-
def _get_GEOM_srid(self, srid):
30-
"Returns the spatial reference identifier (SRID) of the geometry."
31-
warn("use model.geometry_field.srid", DeprecationWarning)
32-
return srid
33-
34-
def _get_GEOM_srs(self, srid):
35-
"Returns ane OGR Spatial Reference object of the geometry."
36-
if HAS_GDAL:
37-
return SpatialReference('EPSG:%d' % srid)
38-
else:
39-
raise Exception, "GDAL is not installed!"
40-
41-
def _get_GEOM_wkt(self, field):
42-
"Returns the WKT of the geometry."
43-
warn("use model.%s.centroid.wkt" % field.attname, DeprecationWarning)
44-
return getattr(self, field.attname).wkt
45-
46-
def _get_GEOM_centroid(self, field):
47-
"Returns the centroid of the geometry, in WKT."
48-
warn("use model.%s.centroid.wkt" % field.attname, DeprecationWarning)
49-
return getattr(self, field.attname).centroid.wkt
50-
51-
def _get_GEOM_area(self, field):
52-
"Returns the area of the geometry, in projected units."
53-
warn("use model.%s.area" % field.attname, DeprecationWarning)
54-
return getattr(self, field.attname).area
4+
"""
5+
The Geographic Mixin class provides routines for geographic objects,
6+
however, it is no longer necessary, since all of its previous functions
7+
may now be accessed via the GeometryProxy. This mixin is only provided
8+
for backwards-compatibility purposes, and will be eventually removed
9+
(unless the need arises again).
10+
"""
11+
pass

django/contrib/gis/db/models/proxy.py

Lines changed: 0 additions & 47 deletions
This file was deleted.

0 commit comments

Comments
 (0)