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
This commit is contained in:
Justin Bronn 2009-04-25 18:24:32 +00:00
parent 8010e0c32e
commit 66db0b43ad
4 changed files with 98 additions and 14 deletions

View File

@ -1 +1,2 @@
from django.forms import *
from django.contrib.gis.forms.fields import GeometryField

View File

@ -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

View File

@ -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.

View File

@ -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()