From 66db0b43ad8759c1d95669847ac8153c36f2ac48 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sat, 25 Apr 2009 18:24:32 +0000 Subject: [PATCH] Fixed #10660 -- `GeometryField` no longer requires `srid`/`null` keywords, and now respects `required`; coordinate transformations now done inside `gis.forms.GeometryField` -- benefit being that `OSMGeoAdmin` no longer requires 900913 entry in `spatial_ref_sys` thus enabling it to work with MySQL/Oracle spatial backends; added tests for geographic forms. git-svn-id: http://code.djangoproject.com/svn/django/trunk@10634 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/forms/__init__.py | 1 + django/contrib/gis/forms/fields.py | 44 ++++++++++----- django/contrib/gis/tests/__init__.py | 2 +- django/contrib/gis/tests/test_geoforms.py | 65 +++++++++++++++++++++++ 4 files changed, 98 insertions(+), 14 deletions(-) create mode 100644 django/contrib/gis/tests/test_geoforms.py diff --git a/django/contrib/gis/forms/__init__.py b/django/contrib/gis/forms/__init__.py index 5441e6078d..82971da6be 100644 --- a/django/contrib/gis/forms/__init__.py +++ b/django/contrib/gis/forms/__init__.py @@ -1 +1,2 @@ +from django.forms import * from django.contrib.gis.forms.fields import GeometryField diff --git a/django/contrib/gis/forms/fields.py b/django/contrib/gis/forms/fields.py index 3e8b271f4b..072dd4766b 100644 --- a/django/contrib/gis/forms/fields.py +++ b/django/contrib/gis/forms/fields.py @@ -1,12 +1,15 @@ from django import forms -from django.contrib.gis.db.backend import SpatialBackend from django.utils.translation import ugettext_lazy as _ +# While this couples the geographic forms to the GEOS library, +# it decouples from database (by not importing SpatialBackend). +from django.contrib.gis.geos import GEOSGeometry + class GeometryField(forms.Field): """ This is the basic form field for a Geometry. Any textual input that is - accepted by SpatialBackend.Geometry is accepted by this form. By default, - this is GEOSGeometry, which accepts WKT, HEXEWKB, WKB, and GeoJSON. + accepted by GEOSGeometry is accepted by this form. By default, + this includes WKT, HEXEWKB, WKB (in a buffer), and GeoJSON. """ widget = forms.Textarea @@ -14,12 +17,16 @@ class GeometryField(forms.Field): 'no_geom' : _(u'No geometry value provided.'), 'invalid_geom' : _(u'Invalid geometry value.'), 'invalid_geom_type' : _(u'Invalid geometry type.'), - } + 'transform_error' : _(u'An error occurred when transforming the geometry' + 'to the SRID of the geometry form field.'), + } def __init__(self, **kwargs): - self.null = kwargs.pop('null') - self.geom_type = kwargs.pop('geom_type') - self.srid = kwargs.pop('srid') + # Pop out attributes from the database field, or use sensible + # defaults (e.g., allow None). + self.srid = kwargs.pop('srid', None) + self.geom_type = kwargs.pop('geom_type', 'GEOMETRY') + self.null = kwargs.pop('null', True) super(GeometryField, self).__init__(**kwargs) def clean(self, value): @@ -29,21 +36,32 @@ class GeometryField(forms.Field): the value cannot be instantiated as a Geometry. """ if not value: - if self.null: - # The geometry column allows NULL, return None. + if self.null and not self.required: + # The geometry column allows NULL and is not required. return None else: raise forms.ValidationError(self.error_messages['no_geom']) - + + # Trying to create a Geometry object from the form value. try: - # Trying to create a Geometry object from the form value. - geom = SpatialBackend.Geometry(value) + geom = GEOSGeometry(value) except: raise forms.ValidationError(self.error_messages['invalid_geom']) - + # Ensuring that the geometry is of the correct type (indicated # using the OGC string label). if str(geom.geom_type).upper() != self.geom_type and not self.geom_type == 'GEOMETRY': raise forms.ValidationError(self.error_messages['invalid_geom_type']) + # Transforming the geometry if the SRID was set. + if self.srid: + if not geom.srid: + # Should match that of the field if not given. + geom.srid = self.srid + elif self.srid != -1 and self.srid != geom.srid: + try: + geom.transform(self.srid) + except: + raise forms.ValidationError(self.error_messages['transform_error']) + return geom diff --git a/django/contrib/gis/tests/__init__.py b/django/contrib/gis/tests/__init__.py index f0305f994f..e5b56cdac8 100644 --- a/django/contrib/gis/tests/__init__.py +++ b/django/contrib/gis/tests/__init__.py @@ -35,7 +35,7 @@ def geo_suite(): if HAS_GDAL: # These tests require GDAL. - test_suite_names.append('test_spatialrefsys') + test_suite_names.extend(['test_spatialrefsys', 'test_geoforms']) test_apps.append('layermap') # Adding the GDAL tests. diff --git a/django/contrib/gis/tests/test_geoforms.py b/django/contrib/gis/tests/test_geoforms.py new file mode 100644 index 0000000000..aa6b25efd3 --- /dev/null +++ b/django/contrib/gis/tests/test_geoforms.py @@ -0,0 +1,65 @@ +import unittest + +from django.forms import ValidationError +from django.contrib.gis import forms +from django.contrib.gis.geos import GEOSGeometry + +class GeometryFieldTest(unittest.TestCase): + + def test00_init(self): + "Testing GeometryField initialization with defaults." + fld = forms.GeometryField() + for bad_default in ('blah', 3, 'FoO', None, 0): + self.assertRaises(ValidationError, fld.clean, bad_default) + + def test01_srid(self): + "Testing GeometryField with a SRID set." + # Input that doesn't specify the SRID is assumed to be in the SRID + # of the input field. + fld = forms.GeometryField(srid=4326) + geom = fld.clean('POINT(5 23)') + self.assertEqual(4326, geom.srid) + # Making the field in a different SRID from that of the geometry, and + # asserting it transforms. + fld = forms.GeometryField(srid=32140) + tol = 0.0000001 + xform_geom = GEOSGeometry('POINT (951640.547328465 4219369.26171664)', srid=32140) + # The cleaned geometry should be transformed to 32140. + cleaned_geom = fld.clean('SRID=4326;POINT (-95.363151 29.763374)') + self.failUnless(xform_geom.equals_exact(cleaned_geom, tol)) + + def test02_null(self): + "Testing GeometryField's handling of null (None) geometries." + # Form fields, by default, are required (`required=True`) + fld = forms.GeometryField() + self.assertRaises(forms.ValidationError, fld.clean, None) + + # Still not allowed if `null=False`. + fld = forms.GeometryField(required=False, null=False) + self.assertRaises(forms.ValidationError, fld.clean, None) + + # This will clean None as a geometry (See #10660). + fld = forms.GeometryField(required=False) + self.assertEqual(None, fld.clean(None)) + + def test03_geom_type(self): + "Testing GeometryField's handling of different geometry types." + # By default, all geometry types are allowed. + fld = forms.GeometryField() + for wkt in ('POINT(5 23)', 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 'LINESTRING(0 0, 1 1)'): + self.assertEqual(GEOSGeometry(wkt), fld.clean(wkt)) + + pnt_fld = forms.GeometryField(geom_type='POINT') + self.assertEqual(GEOSGeometry('POINT(5 23)'), pnt_fld.clean('POINT(5 23)')) + self.assertRaises(forms.ValidationError, pnt_fld.clean, 'LINESTRING(0 0, 1 1)') + +def suite(): + s = unittest.TestSuite() + s.addTest(unittest.makeSuite(GeometryFieldTest)) + return s + +def run(verbosity=2): + unittest.TextTestRunner(verbosity=verbosity).run(suite()) + +if __name__=="__main__": + run()