From b6c8bba5b6b72ce7693f671d2f4bdb1a3a3f4737 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Mon, 8 Oct 2007 18:18:17 +0000 Subject: [PATCH] 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 --- django/contrib/gis/db/backend/__init__.py | 28 ++++--- .../gis/db/backend/postgis/__init__.py | 1 + .../contrib/gis/db/backend/postgis/field.py | 29 +++++-- .../contrib/gis/db/backend/postgis/models.py | 54 +++++++++++++ .../contrib/gis/db/backend/postgis/proxy.py | 62 ++++++++++++++ .../contrib/gis/db/models/fields/__init__.py | 29 ++----- django/contrib/gis/db/models/mixin.py | 59 ++------------ django/contrib/gis/db/models/proxy.py | 47 ----------- django/contrib/gis/models.py | 80 +++++++------------ django/contrib/gis/tests/geoapp/models.py | 17 ++-- django/contrib/gis/tests/geoapp/tests.py | 42 +++++++++- django/core/management/sql.py | 2 +- 12 files changed, 251 insertions(+), 199 deletions(-) create mode 100644 django/contrib/gis/db/backend/postgis/models.py create mode 100644 django/contrib/gis/db/backend/postgis/proxy.py delete mode 100644 django/contrib/gis/db/models/proxy.py diff --git a/django/contrib/gis/db/backend/__init__.py b/django/contrib/gis/db/backend/__init__.py index f3e4e8ecdc..a28a8d1cff 100644 --- a/django/contrib/gis/db/backend/__init__.py +++ b/django/contrib/gis/db/backend/__init__.py @@ -1,15 +1,21 @@ """ - This module provides the backend for spatial SQL construction with Django. + This module provides the backend for spatial SQL construction with Django. - Specifically, this module will import the correct routines and modules - needed for GeoDjango + Specifically, this module will import the correct routines and modules + needed for GeoDjango. + + (1) GeoBackEndField, a base class needed for GeometryField. + (2) GeometryProxy, for lazy-instantiated geometries from the + database output. + (3) GIS_TERMS, a list of acceptable geographic lookup types for + the backend. + (4) The `parse_lookup` function, used for spatial SQL construction by + the GeoQuerySet. + (5) The `create_spatial_db`, `geo_quotename`, and `get_geo_where_clause` + routines (needed by `parse_lookup`. - (1) GeoBackEndField, a base class needed for GeometryField. - (2) The parse_lookup() function, used for spatial SQL construction by - the GeoQuerySet. - - Currently only PostGIS is supported, but someday backends will be added for - additional spatial databases (e.g., Oracle, DB2). + Currently only PostGIS is supported, but someday backends will be added for + additional spatial databases (e.g., Oracle, DB2). """ from django.conf import settings from django.db import connection @@ -21,9 +27,11 @@ from django.utils.datastructures import SortedDict ASGML, ASKML, UNION = (False, False, False) if settings.DATABASE_ENGINE == 'postgresql_psycopg2': - # PostGIS is the spatial database, getting the rquired modules, renaming as necessary. + # PostGIS is the spatial database, getting the rquired modules, + # renaming as necessary. from django.contrib.gis.db.backend.postgis import \ PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \ + PostGISProxy as GeometryProxy, \ create_spatial_db, geo_quotename, get_geo_where_clause, \ ASGML, ASKML, UNION else: diff --git a/django/contrib/gis/db/backend/postgis/__init__.py b/django/contrib/gis/db/backend/postgis/__init__.py index 33ef9a43fa..d95bde4370 100644 --- a/django/contrib/gis/db/backend/postgis/__init__.py +++ b/django/contrib/gis/db/backend/postgis/__init__.py @@ -7,6 +7,7 @@ from django.contrib.gis.db.backend.postgis.query import \ MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 from django.contrib.gis.db.backend.postgis.creation import create_spatial_db from django.contrib.gis.db.backend.postgis.field import PostGISField +from django.contrib.gis.db.backend.postgis.proxy import PostGISProxy # Functions used by GeoManager methods, and not via lookup types. if MAJOR_VERSION == 1: diff --git a/django/contrib/gis/db/backend/postgis/field.py b/django/contrib/gis/db/backend/postgis/field.py index c1cfc5e97e..2d941dfd0b 100644 --- a/django/contrib/gis/db/backend/postgis/field.py +++ b/django/contrib/gis/db/backend/postgis/field.py @@ -44,9 +44,11 @@ class PostGISField(Field): return sql def _post_create_sql(self, style, db_table): - """Returns SQL that will be executed after the model has been + """ + Returns SQL that will be executed after the model has been created. Geometry columns must be added after creation with the - PostGIS AddGeometryColumn() function.""" + PostGIS AddGeometryColumn() function. + """ # Getting the AddGeometryColumn() SQL necessary to create a PostGIS # geometry field. @@ -54,9 +56,9 @@ class PostGISField(Field): # If the user wants to index this data, then get the indexing SQL as well. if self._index: - return '%s\n%s' % (post_sql, self._geom_index(style, db_table)) + return (post_sql, self._geom_index(style, db_table)) else: - return post_sql + return (post_sql,) def _post_delete_sql(self, style, db_table): "Drops the geometry column." @@ -66,9 +68,18 @@ class PostGISField(Field): style.SQL_FIELD(quotename(self.column)) + ');' return sql + def db_type(self): + """ + PostGIS geometry columns are added by stored procedures, should be + None. + """ + return None + def get_db_prep_lookup(self, lookup_type, value): - """Returns field's value prepared for database lookup, accepts WKT and - GEOS Geometries for the value.""" + """ + Returns field's value prepared for database lookup, accepts WKT and + GEOS Geometries for the value. + """ if lookup_type in POSTGIS_TERMS: if lookup_type == 'isnull': return [value] # special case for NULL geometries. if not bool(value): return [None] # If invalid value passed in. @@ -101,6 +112,12 @@ class PostGISField(Field): else: raise TypeError('Geometry Proxy should only return GEOSGeometry objects.') + def get_internal_type(self): + """ + Returns NoField because a stored procedure is used by PostGIS to create the + """ + return 'NoField' + def get_placeholder(self, value): """ Provides a proper substitution value for Geometries that are not in the diff --git a/django/contrib/gis/db/backend/postgis/models.py b/django/contrib/gis/db/backend/postgis/models.py new file mode 100644 index 0000000000..4071dc646b --- /dev/null +++ b/django/contrib/gis/db/backend/postgis/models.py @@ -0,0 +1,54 @@ +""" + The GeometryColumns and SpatialRefSys models for the PostGIS backend. +""" +from django.db import models +from django.contrib.gis.models import SpatialRefSysMixin + +# Checking for the presence of GDAL (needed for the SpatialReference object) +from django.contrib.gis.gdal import HAS_GDAL +if HAS_GDAL: + from django.contrib.gis.gdal import SpatialReference + +class GeometryColumns(models.Model): + """ + The 'geometry_columns' table from the PostGIS. See the PostGIS + documentation at Ch. 4.2.2. + """ + f_table_catalog = models.CharField(maxlength=256) + f_table_schema = models.CharField(maxlength=256) + f_table_name = models.CharField(maxlength=256, primary_key=True) + f_geometry_column = models.CharField(maxlength=256) + coord_dimension = models.IntegerField() + srid = models.IntegerField() + type = models.CharField(maxlength=30) + + class Meta: + db_table = 'geometry_columns' + + @classmethod + def table_name(self): + "Class method for returning the table name field for this model." + return 'f_table_name' + + def __unicode__(self): + return "%s.%s - %dD %s field (SRID: %d)" % \ + (self.f_table_name, self.f_geometry_column, + self.coord_dimension, self.type, self.srid) + +class SpatialRefSys(models.Model, SpatialRefSysMixin): + """ + The 'spatial_ref_sys' table from PostGIS. See the PostGIS + documentaiton at Ch. 4.2.1. + """ + srid = models.IntegerField(primary_key=True) + auth_name = models.CharField(maxlength=256) + auth_srid = models.IntegerField() + srtext = models.CharField(maxlength=2048) + proj4text = models.CharField(maxlength=2048) + + class Meta: + db_table = 'spatial_ref_sys' + + @property + def wkt(self): + return self.srtext diff --git a/django/contrib/gis/db/backend/postgis/proxy.py b/django/contrib/gis/db/backend/postgis/proxy.py new file mode 100644 index 0000000000..bb1ee7f280 --- /dev/null +++ b/django/contrib/gis/db/backend/postgis/proxy.py @@ -0,0 +1,62 @@ +""" + The GeometryProxy object, allows for lazy-geometries. The proxy uses + Python descriptors for instantiating and setting GEOS Geometry objects + corresponding to geographic model fields. + + Thanks to Robert Coup for providing this functionality (see #4322). +""" + +from types import NoneType, StringType, UnicodeType + +class PostGISProxy(object): + def __init__(self, klass, field): + """ + Proxy initializes on the given Geometry class (not an instance) and + the GeometryField. + """ + self._field = field + self._klass = klass + + def __get__(self, obj, type=None): + """ + This accessor retrieves the geometry, initializing it using the geometry + class specified during initialization and the HEXEWKB value of the field. + Currently, only GEOS or OGR geometries are supported. + """ + # Getting the value of the field. + geom_value = obj.__dict__[self._field.attname] + + if isinstance(geom_value, self._klass): + geom = geom_value + elif (geom_value is None) or (geom_value==''): + geom = None + else: + # Otherwise, a GEOSGeometry object is built using the field's contents, + # and the model's corresponding attribute is set. + geom = self._klass(geom_value) + setattr(obj, self._field.attname, geom) + return geom + + def __set__(self, obj, value): + """ + This accessor sets the proxied geometry with the geometry class + specified during initialization. Values of None, HEXEWKB, or WKT may + be used to set the geometry as well. + """ + # The OGC Geometry type of the field. + gtype = self._field._geom + + # The geometry type must match that of the field -- unless the + # general GeometryField is used. + if isinstance(value, self._klass) and (str(value.geom_type).upper() == gtype or gtype == 'GEOMETRY'): + # Assigning the SRID to the geometry. + if value.srid is None: value.srid = self._field._srid + elif isinstance(value, (NoneType, StringType, UnicodeType)): + # Set with None, WKT, or HEX + pass + else: + raise TypeError('cannot set %s GeometryProxy with value of type: %s' % (obj.__class__.__name__, type(value))) + + # Setting the objects dictionary with the value, and returning. + obj.__dict__[self._field.attname] = value + return value diff --git a/django/contrib/gis/db/models/fields/__init__.py b/django/contrib/gis/db/models/fields/__init__.py index 8d329fd97c..850cac2007 100644 --- a/django/contrib/gis/db/models/fields/__init__.py +++ b/django/contrib/gis/db/models/fields/__init__.py @@ -1,9 +1,9 @@ -from django.contrib.gis.db.backend import GeoBackendField # depends on the spatial database backend. -from django.contrib.gis.db.models.proxy import GeometryProxy +from django.conf import settings +from django.contrib.gis.db.backend import GeoBackendField, GeometryProxy # these depend on the spatial database backend. from django.contrib.gis.oldforms import WKTField -from django.utils.functional import curry +from django.contrib.gis.geos import GEOSGeometry -#TODO: Flesh out widgets. +#TODO: Flesh out widgets; consider adding support for OGR Geometry proxies. class GeometryField(GeoBackendField): "The base GIS field -- maps to the OpenGIS Specification Geometry type." @@ -31,26 +31,9 @@ class GeometryField(GeoBackendField): def contribute_to_class(self, cls, name): super(GeometryField, self).contribute_to_class(cls, name) - # setup for lazy-instantiated GEOSGeometry objects - setattr(cls, self.attname, GeometryProxy(self)) - - # Adding needed accessor functions - setattr(cls, 'get_%s_geos' % self.name, curry(cls._get_GEOM_geos, field=self)) - setattr(cls, 'get_%s_ogr' % self.name, curry(cls._get_GEOM_ogr, field=self, srid=self._srid)) - setattr(cls, 'get_%s_srid' % self.name, curry(cls._get_GEOM_srid, srid=self._srid)) - setattr(cls, 'get_%s_srs' % self.name, curry(cls._get_GEOM_srs, srid=self._srid)) - setattr(cls, 'get_%s_wkt' % self.name, curry(cls._get_GEOM_wkt, field=self)) - setattr(cls, 'get_%s_centroid' % self.name, curry(cls._get_GEOM_centroid, field=self)) - setattr(cls, 'get_%s_area' % self.name, curry(cls._get_GEOM_area, field=self)) + # Setup for lazy-instantiated GEOSGeometry object. + setattr(cls, self.attname, GeometryProxy(GEOSGeometry, self)) - def get_internal_type(self): - return "NoField" - - def db_type(self): - # Geometry columns are added by stored procedures, and thus should - # be None. - return None - def get_manipulator_field_objs(self): "Using the WKTField (defined above) to be our manipulator." return [WKTField] diff --git a/django/contrib/gis/db/models/mixin.py b/django/contrib/gis/db/models/mixin.py index 90640d1787..475a053b8f 100644 --- a/django/contrib/gis/db/models/mixin.py +++ b/django/contrib/gis/db/models/mixin.py @@ -1,54 +1,11 @@ -from warnings import warn - -# GEOS is a requirement, GDAL is not -from django.contrib.gis.geos import GEOSGeometry -from django.contrib.gis.gdal import HAS_GDAL -if HAS_GDAL: - from django.contrib.gis.gdal import OGRGeometry, SpatialReference - # Until model subclassing is a possibility, a mixin class is used to add # the necessary functions that may be contributed for geographic objects. class GeoMixin: - "The Geographic Mixin class provides routines for geographic objects." - - # A subclass of Model is specifically needed so that these geographic - # routines are present for instantiations of the models. - def _get_GEOM_geos(self, field): - "Returns a GEOS Python object for the geometry." - warn("use model.%s" % field.attname, DeprecationWarning) - return getattr(self, field.attname) - - def _get_GEOM_ogr(self, field, srid): - "Returns an OGR Python object for the geometry." - if HAS_GDAL: - return OGRGeometry(getattr(self, field.attname).wkt, - SpatialReference('EPSG:%d' % srid)) - else: - raise Exception, "GDAL is not installed!" - - def _get_GEOM_srid(self, srid): - "Returns the spatial reference identifier (SRID) of the geometry." - warn("use model.geometry_field.srid", DeprecationWarning) - return srid - - def _get_GEOM_srs(self, srid): - "Returns ane OGR Spatial Reference object of the geometry." - if HAS_GDAL: - return SpatialReference('EPSG:%d' % srid) - else: - raise Exception, "GDAL is not installed!" - - def _get_GEOM_wkt(self, field): - "Returns the WKT of the geometry." - warn("use model.%s.centroid.wkt" % field.attname, DeprecationWarning) - return getattr(self, field.attname).wkt - - def _get_GEOM_centroid(self, field): - "Returns the centroid of the geometry, in WKT." - warn("use model.%s.centroid.wkt" % field.attname, DeprecationWarning) - return getattr(self, field.attname).centroid.wkt - - def _get_GEOM_area(self, field): - "Returns the area of the geometry, in projected units." - warn("use model.%s.area" % field.attname, DeprecationWarning) - return getattr(self, field.attname).area + """ + The Geographic Mixin class provides routines for geographic objects, + however, it is no longer necessary, since all of its previous functions + may now be accessed via the GeometryProxy. This mixin is only provided + for backwards-compatibility purposes, and will be eventually removed + (unless the need arises again). + """ + pass diff --git a/django/contrib/gis/db/models/proxy.py b/django/contrib/gis/db/models/proxy.py deleted file mode 100644 index 65c613da26..0000000000 --- a/django/contrib/gis/db/models/proxy.py +++ /dev/null @@ -1,47 +0,0 @@ -""" - The GeometryProxy object, allows for lazy-geometries. The proxy uses - Python descriptors for instantiating and setting GEOS Geometry objects - corresponding to geographic model fields. - - Thanks to Robert Coup for providing this functionality (see #4322). -""" - -from types import NoneType, StringType, UnicodeType -from django.contrib.gis.geos import GEOSGeometry, GEOSException - -# TODO: docstrings -class GeometryProxy(object): - def __init__(self, field): - "Proxy initializes on the given GeometryField." - self._field = field - - def __get__(self, obj, type=None): - # Getting the value of the field. - geom_value = obj.__dict__[self._field.attname] - - if isinstance(geom_value, GEOSGeometry): - # If the value of the field is None, or is already a GEOS Geometry - # no more work is needed. - geom = geom_value - elif (geom_value is None) or (geom_value==''): - geom = None - else: - # Otherwise, a GEOSGeometry object is built using the field's contents, - # and the model's corresponding attribute is set. - geom = GEOSGeometry(geom_value) - setattr(obj, self._field.attname, geom) - return geom - - def __set__(self, obj, value): - if isinstance(value, GEOSGeometry) and (value.geom_type.upper() == self._field._geom): - # Getting set with GEOS Geometry; geom_type must match that of the field. - - # If value's SRID is not set, setting it to the field's SRID. - if value.srid is None: value.srid = self._field._srid - elif isinstance(value, (NoneType, StringType, UnicodeType)): - # Getting set with None, WKT, or HEX - pass - else: - raise TypeError, 'cannot set %s GeometryProxy with value of type: %s' % (self._field._geom, type(value)) - obj.__dict__[self._field.attname] = value - return value diff --git a/django/contrib/gis/models.py b/django/contrib/gis/models.py index 91ca8d7818..5a4e480837 100644 --- a/django/contrib/gis/models.py +++ b/django/contrib/gis/models.py @@ -1,8 +1,9 @@ """ - Models for the PostGIS/OGC database tables. + Imports the SpatialRefSys and GeometryColumns models dependent on the + spatial database backend. """ import re -from django.db import models +from django.conf import settings # Checking for the presence of GDAL (needed for the SpatialReference object) from django.contrib.gis.gdal import HAS_GDAL @@ -15,37 +16,11 @@ if HAS_GDAL: # parameter. spheroid_regex = re.compile(r'.+SPHEROID\[\"(?P.+)\",(?P\d+(\.\d+)?),(?P\d{3}\.\d+),') -# This is the global 'geometry_columns' from PostGIS. -# See PostGIS Documentation at Ch. 4.2.2 -class GeometryColumns(models.Model): - f_table_catalog = models.CharField(maxlength=256) - f_table_schema = models.CharField(maxlength=256) - f_table_name = models.CharField(maxlength=256, primary_key=True) - f_geometry_column = models.CharField(maxlength=256) - coord_dimension = models.IntegerField() - srid = models.IntegerField() - type = models.CharField(maxlength=30) - - class Meta: - db_table = 'geometry_columns' - - def __str__(self): - return "%s.%s - %dD %s field (SRID: %d)" % \ - (self.f_table_name, self.f_geometry_column, - self.coord_dimension, self.type, self.srid) - -# This is the global 'spatial_ref_sys' table from PostGIS. -# See PostGIS Documentation at Ch. 4.2.1 -class SpatialRefSys(models.Model): - srid = models.IntegerField(primary_key=True) - auth_name = models.CharField(maxlength=256) - auth_srid = models.IntegerField() - srtext = models.CharField(maxlength=2048) - proj4text = models.CharField(maxlength=2048) - - class Meta: - db_table = 'spatial_ref_sys' - +class SpatialRefSysMixin(object): + """ + The SpatialRefSysMixin is a class used by the database-dependent + SpatialRefSys objects to reduce redundnant code. + """ @property def srs(self): """ @@ -58,24 +33,17 @@ class SpatialRefSys(models.Model): else: # Attempting to cache a SpatialReference object. - # Trying to get from WKT first + # Trying to get from WKT first. try: - self._srs = SpatialReference(self.srtext, 'wkt') - return self._srs.clone() - except Exception, msg1: + self._srs = SpatialReference(self.wkt) + return self.srs + except Exception, msg: pass - - # Trying the proj4 text next - try: - self._srs = SpatialReference(self.proj4text, 'proj4') - return self._srs.clone() - except Exception, msg2: - pass - - raise Exception, 'Could not get an OSR Spatial Reference:\n\tWKT error: %s\n\tPROJ.4 error: %s' % (msg1, msg2) + + raise Exception('Could not get OSR SpatialReference from WKT: %s\nError:\n%s' % (self.wkt, msg)) else: - raise Exception, 'GDAL is not installed!' - + raise Exception('GDAL is not installed.') + @property def ellipsoid(self): """ @@ -85,7 +53,7 @@ class SpatialRefSys(models.Model): if HAS_GDAL: return self.srs.ellipsoid else: - m = spheroid_regex.match(self.srtext) + m = spheroid_regex.match(self.wkt) if m: return (float(m.group('major')), float(m.group('flattening'))) else: return None @@ -139,10 +107,18 @@ class SpatialRefSys(models.Model): "Returns the name of the angular units." return self.srs.angular_name - def __str__(self): + def __unicode__(self): """ Returns the string representation. If GDAL is installed, it will be 'pretty' OGC WKT. """ - if HAS_GDAL: return str(self.srs) - else: return "%d:%s " % (self.srid, self.auth_name) + try: + return unicode(self.srs) + except: + return unicode(self.srtext) + +# The SpatialRefSys and GeometryColumns models +if settings.DATABASE_ENGINE == 'postgresql_psycopg2': + from django.contrib.gis.db.backend.postgis.models import GeometryColumns, SpatialRefSys +else: + raise NotImplementedError('No SpatialRefSys or GeometryColumns models for backend: %s' % settings.DATABASE_ENGINE) diff --git a/django/contrib/gis/tests/geoapp/models.py b/django/contrib/gis/tests/geoapp/models.py index fa979beead..4315b4247c 100644 --- a/django/contrib/gis/tests/geoapp/models.py +++ b/django/contrib/gis/tests/geoapp/models.py @@ -1,16 +1,21 @@ from django.contrib.gis.db import models -class Country(models.Model, models.GeoMixin): - name = models.CharField(maxlength=30) +class Country(models.Model): + name = models.CharField(max_length=30) mpoly = models.MultiPolygonField() # SRID, by default, is 4326 objects = models.GeoManager() -class City(models.Model, models.GeoMixin): - name = models.CharField(maxlength=30) +class City(models.Model): + name = models.CharField(max_length=30) point = models.PointField() objects = models.GeoManager() -class State(models.Model, models.GeoMixin): - name = models.CharField(maxlength=30) +class State(models.Model): + name = models.CharField(max_length=30) poly = models.PolygonField(null=True) # Allowing NULL geometries here. objects = models.GeoManager() + +class Feature(models.Model): + name = models.CharField(max_length=20) + geom = models.GeometryField() + objects = models.GeoManager() diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py index e8127c0827..004e75898e 100644 --- a/django/contrib/gis/tests/geoapp/tests.py +++ b/django/contrib/gis/tests/geoapp/tests.py @@ -1,6 +1,7 @@ import unittest -from models import Country, City, State -from django.contrib.gis.geos import fromstr, Point, LineString, LinearRing, Polygon +from models import Country, City, State, Feature +from django.contrib.gis.geos import * +from django.contrib.gis import gdal class GeoModelTest(unittest.TestCase): @@ -59,8 +60,17 @@ class GeoModelTest(unittest.TestCase): nullstate = State(name='NullState', poly=ply) self.assertEqual(4326, nullstate.poly.srid) # SRID auto-set from None nullstate.save() - self.assertEqual(ply, State.objects.get(name='NullState').poly) + + ns = State.objects.get(name='NullState') + self.assertEqual(ply, ns.poly) + # Testing the `ogr` and `srs` lazy-geometry properties. + if gdal.HAS_GDAL: + self.assertEqual(True, isinstance(ns.poly.ogr, gdal.OGRGeometry)) + self.assertEqual(ns.poly.wkb, ns.poly.ogr.wkb) + self.assertEqual(True, isinstance(ns.poly.srs, gdal.SpatialReference)) + self.assertEqual('WGS 84', ns.poly.srs.name) + # Changing the interior ring on the poly attribute. new_inner = LinearRing((30, 30), (30, 70), (70, 70), (70, 30), (30, 30)) nullstate.poly[1] = new_inner.clone() @@ -277,6 +287,32 @@ class GeoModelTest(unittest.TestCase): self.assertEqual(True, union.equals_exact(u, 10)) # Going up to 10 digits of precision. qs = City.objects.filter(name='NotACity') self.assertEqual(None, qs.union('point')) + + def test18_geometryfield(self): + "Testing GeometryField." + f1 = Feature(name='Point', geom=Point(1, 1)) + f2 = Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5))) + f3 = Feature(name='Polygon', geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)))) + f4 = Feature(name='GeometryCollection', + geom=GeometryCollection(Point(2, 2), LineString((0, 0), (2, 2)), + Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))))) + f1.save() + f2.save() + f3.save() + f4.save() + + f_1 = Feature.objects.get(name='Point') + self.assertEqual(True, isinstance(f_1.geom, Point)) + self.assertEqual((1.0, 1.0), f_1.geom.tuple) + f_2 = Feature.objects.get(name='LineString') + self.assertEqual(True, isinstance(f_2.geom, LineString)) + self.assertEqual(((0.0, 0.0), (1.0, 1.0), (5.0, 5.0)), f_2.geom.tuple) + + f_3 = Feature.objects.get(name='Polygon') + self.assertEqual(True, isinstance(f_3.geom, Polygon)) + f_4 = Feature.objects.get(name='GeometryCollection') + self.assertEqual(True, isinstance(f_4.geom, GeometryCollection)) + self.assertEqual(f_3.geom, f_4.geom[2]) def suite(): s = unittest.TestSuite() diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 1757d4d7a0..d69d83e44e 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -404,7 +404,7 @@ def custom_sql_for_model(model, style): # Post-creation SQL should come before any initial SQL data is loaded. for f in opts.fields: if hasattr(f, '_post_create_sql'): - output.append(f._post_create_sql(style, model._meta.db_table)) + output.extend(f._post_create_sql(style, model._meta.db_table)) # Some backends can't execute more than one SQL statement at a time, # so split into separate statements.