test_geoforms.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. from unittest import skipUnless
  2. from django.contrib.gis.gdal import HAS_GDAL
  3. from django.forms import ValidationError
  4. from django.test import SimpleTestCase, skipUnlessDBFeature
  5. from django.utils import six
  6. from django.utils.html import escape
  7. if HAS_GDAL:
  8. from django.contrib.gis import forms
  9. from django.contrib.gis.geos import GEOSGeometry
  10. @skipUnless(HAS_GDAL, "GeometryFieldTest needs GDAL support")
  11. @skipUnlessDBFeature("gis_enabled")
  12. class GeometryFieldTest(SimpleTestCase):
  13. def test_init(self):
  14. "Testing GeometryField initialization with defaults."
  15. fld = forms.GeometryField()
  16. for bad_default in ('blah', 3, 'FoO', None, 0):
  17. self.assertRaises(ValidationError, fld.clean, bad_default)
  18. def test_srid(self):
  19. "Testing GeometryField with a SRID set."
  20. # Input that doesn't specify the SRID is assumed to be in the SRID
  21. # of the input field.
  22. fld = forms.GeometryField(srid=4326)
  23. geom = fld.clean('POINT(5 23)')
  24. self.assertEqual(4326, geom.srid)
  25. # Making the field in a different SRID from that of the geometry, and
  26. # asserting it transforms.
  27. fld = forms.GeometryField(srid=32140)
  28. tol = 0.0000001
  29. xform_geom = GEOSGeometry('POINT (951640.547328465 4219369.26171664)', srid=32140)
  30. # The cleaned geometry should be transformed to 32140.
  31. cleaned_geom = fld.clean('SRID=4326;POINT (-95.363151 29.763374)')
  32. self.assertTrue(xform_geom.equals_exact(cleaned_geom, tol))
  33. def test_null(self):
  34. "Testing GeometryField's handling of null (None) geometries."
  35. # Form fields, by default, are required (`required=True`)
  36. fld = forms.GeometryField()
  37. with six.assertRaisesRegex(self, forms.ValidationError,
  38. "No geometry value provided."):
  39. fld.clean(None)
  40. # This will clean None as a geometry (See #10660).
  41. fld = forms.GeometryField(required=False)
  42. self.assertIsNone(fld.clean(None))
  43. def test_geom_type(self):
  44. "Testing GeometryField's handling of different geometry types."
  45. # By default, all geometry types are allowed.
  46. fld = forms.GeometryField()
  47. for wkt in ('POINT(5 23)', 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 'LINESTRING(0 0, 1 1)'):
  48. self.assertEqual(GEOSGeometry(wkt), fld.clean(wkt))
  49. pnt_fld = forms.GeometryField(geom_type='POINT')
  50. self.assertEqual(GEOSGeometry('POINT(5 23)'), pnt_fld.clean('POINT(5 23)'))
  51. # a WKT for any other geom_type will be properly transformed by `to_python`
  52. self.assertEqual(GEOSGeometry('LINESTRING(0 0, 1 1)'), pnt_fld.to_python('LINESTRING(0 0, 1 1)'))
  53. # but rejected by `clean`
  54. self.assertRaises(forms.ValidationError, pnt_fld.clean, 'LINESTRING(0 0, 1 1)')
  55. def test_to_python(self):
  56. """
  57. Testing to_python returns a correct GEOSGeometry object or
  58. a ValidationError
  59. """
  60. fld = forms.GeometryField()
  61. # to_python returns the same GEOSGeometry for a WKT
  62. for wkt in ('POINT(5 23)', 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 'LINESTRING(0 0, 1 1)'):
  63. self.assertEqual(GEOSGeometry(wkt), fld.to_python(wkt))
  64. # but raises a ValidationError for any other string
  65. for wkt in ('POINT(5)', 'MULTI POLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 'BLAH(0 0, 1 1)'):
  66. self.assertRaises(forms.ValidationError, fld.to_python, wkt)
  67. def test_field_with_text_widget(self):
  68. class PointForm(forms.Form):
  69. pt = forms.PointField(srid=4326, widget=forms.TextInput)
  70. form = PointForm()
  71. cleaned_pt = form.fields['pt'].clean('POINT(5 23)')
  72. self.assertEqual(cleaned_pt, GEOSGeometry('POINT(5 23)'))
  73. self.assertEqual(4326, cleaned_pt.srid)
  74. point = GEOSGeometry('SRID=4326;POINT(5 23)')
  75. form = PointForm(data={'pt': 'POINT(5 23)'}, initial={'pt': point})
  76. self.assertFalse(form.has_changed())
  77. @skipUnless(HAS_GDAL, "SpecializedFieldTest needs GDAL support")
  78. @skipUnlessDBFeature("gis_enabled")
  79. class SpecializedFieldTest(SimpleTestCase):
  80. def setUp(self):
  81. self.geometries = {
  82. 'point': GEOSGeometry("SRID=4326;POINT(9.052734375 42.451171875)"),
  83. 'multipoint': GEOSGeometry("SRID=4326;MULTIPOINT("
  84. "(13.18634033203125 14.504356384277344),"
  85. "(13.207969665527 14.490966796875),"
  86. "(13.177070617675 14.454917907714))"),
  87. 'linestring': GEOSGeometry("SRID=4326;LINESTRING("
  88. "-8.26171875 -0.52734375,"
  89. "-7.734375 4.21875,"
  90. "6.85546875 3.779296875,"
  91. "5.44921875 -3.515625)"),
  92. 'multilinestring': GEOSGeometry("SRID=4326;MULTILINESTRING("
  93. "(-16.435546875 -2.98828125,"
  94. "-17.2265625 2.98828125,"
  95. "-0.703125 3.515625,"
  96. "-1.494140625 -3.33984375),"
  97. "(-8.0859375 -5.9765625,"
  98. "8.525390625 -8.7890625,"
  99. "12.392578125 -0.87890625,"
  100. "10.01953125 7.646484375))"),
  101. 'polygon': GEOSGeometry("SRID=4326;POLYGON("
  102. "(-1.669921875 6.240234375,"
  103. "-3.8671875 -0.615234375,"
  104. "5.9765625 -3.955078125,"
  105. "18.193359375 3.955078125,"
  106. "9.84375 9.4921875,"
  107. "-1.669921875 6.240234375))"),
  108. 'multipolygon': GEOSGeometry("SRID=4326;MULTIPOLYGON("
  109. "((-17.578125 13.095703125,"
  110. "-17.2265625 10.8984375,"
  111. "-13.974609375 10.1953125,"
  112. "-13.359375 12.744140625,"
  113. "-15.732421875 13.7109375,"
  114. "-17.578125 13.095703125)),"
  115. "((-8.525390625 5.537109375,"
  116. "-8.876953125 2.548828125,"
  117. "-5.888671875 1.93359375,"
  118. "-5.09765625 4.21875,"
  119. "-6.064453125 6.240234375,"
  120. "-8.525390625 5.537109375)))"),
  121. 'geometrycollection': GEOSGeometry("SRID=4326;GEOMETRYCOLLECTION("
  122. "POINT(5.625 -0.263671875),"
  123. "POINT(6.767578125 -3.603515625),"
  124. "POINT(8.525390625 0.087890625),"
  125. "POINT(8.0859375 -2.13134765625),"
  126. "LINESTRING("
  127. "6.273193359375 -1.175537109375,"
  128. "5.77880859375 -1.812744140625,"
  129. "7.27294921875 -2.230224609375,"
  130. "7.657470703125 -1.25244140625))"),
  131. }
  132. def assertMapWidget(self, form_instance):
  133. """
  134. Make sure the MapWidget js is passed in the form media and a MapWidget
  135. is actually created
  136. """
  137. self.assertTrue(form_instance.is_valid())
  138. rendered = form_instance.as_p()
  139. self.assertIn('new MapWidget(options);', rendered)
  140. self.assertIn('gis/js/OLMapWidget.js', str(form_instance.media))
  141. def assertTextarea(self, geom, rendered):
  142. """Makes sure the wkt and a textarea are in the content"""
  143. self.assertIn('<textarea ', rendered)
  144. self.assertIn('required', rendered)
  145. self.assertIn(geom.wkt, rendered)
  146. def test_pointfield(self):
  147. class PointForm(forms.Form):
  148. p = forms.PointField()
  149. geom = self.geometries['point']
  150. form = PointForm(data={'p': geom})
  151. self.assertTextarea(geom, form.as_p())
  152. self.assertMapWidget(form)
  153. self.assertFalse(PointForm().is_valid())
  154. invalid = PointForm(data={'p': 'some invalid geom'})
  155. self.assertFalse(invalid.is_valid())
  156. self.assertIn('Invalid geometry value', str(invalid.errors))
  157. for invalid in [geo for key, geo in self.geometries.items() if key != 'point']:
  158. self.assertFalse(PointForm(data={'p': invalid.wkt}).is_valid())
  159. def test_multipointfield(self):
  160. class PointForm(forms.Form):
  161. p = forms.MultiPointField()
  162. geom = self.geometries['multipoint']
  163. form = PointForm(data={'p': geom})
  164. self.assertTextarea(geom, form.as_p())
  165. self.assertMapWidget(form)
  166. self.assertFalse(PointForm().is_valid())
  167. for invalid in [geo for key, geo in self.geometries.items() if key != 'multipoint']:
  168. self.assertFalse(PointForm(data={'p': invalid.wkt}).is_valid())
  169. def test_linestringfield(self):
  170. class LineStringForm(forms.Form):
  171. l = forms.LineStringField()
  172. geom = self.geometries['linestring']
  173. form = LineStringForm(data={'l': geom})
  174. self.assertTextarea(geom, form.as_p())
  175. self.assertMapWidget(form)
  176. self.assertFalse(LineStringForm().is_valid())
  177. for invalid in [geo for key, geo in self.geometries.items() if key != 'linestring']:
  178. self.assertFalse(LineStringForm(data={'p': invalid.wkt}).is_valid())
  179. def test_multilinestringfield(self):
  180. class LineStringForm(forms.Form):
  181. l = forms.MultiLineStringField()
  182. geom = self.geometries['multilinestring']
  183. form = LineStringForm(data={'l': geom})
  184. self.assertTextarea(geom, form.as_p())
  185. self.assertMapWidget(form)
  186. self.assertFalse(LineStringForm().is_valid())
  187. for invalid in [geo for key, geo in self.geometries.items() if key != 'multilinestring']:
  188. self.assertFalse(LineStringForm(data={'p': invalid.wkt}).is_valid())
  189. def test_polygonfield(self):
  190. class PolygonForm(forms.Form):
  191. p = forms.PolygonField()
  192. geom = self.geometries['polygon']
  193. form = PolygonForm(data={'p': geom})
  194. self.assertTextarea(geom, form.as_p())
  195. self.assertMapWidget(form)
  196. self.assertFalse(PolygonForm().is_valid())
  197. for invalid in [geo for key, geo in self.geometries.items() if key != 'polygon']:
  198. self.assertFalse(PolygonForm(data={'p': invalid.wkt}).is_valid())
  199. def test_multipolygonfield(self):
  200. class PolygonForm(forms.Form):
  201. p = forms.MultiPolygonField()
  202. geom = self.geometries['multipolygon']
  203. form = PolygonForm(data={'p': geom})
  204. self.assertTextarea(geom, form.as_p())
  205. self.assertMapWidget(form)
  206. self.assertFalse(PolygonForm().is_valid())
  207. for invalid in [geo for key, geo in self.geometries.items() if key != 'multipolygon']:
  208. self.assertFalse(PolygonForm(data={'p': invalid.wkt}).is_valid())
  209. def test_geometrycollectionfield(self):
  210. class GeometryForm(forms.Form):
  211. g = forms.GeometryCollectionField()
  212. geom = self.geometries['geometrycollection']
  213. form = GeometryForm(data={'g': geom})
  214. self.assertTextarea(geom, form.as_p())
  215. self.assertMapWidget(form)
  216. self.assertFalse(GeometryForm().is_valid())
  217. for invalid in [geo for key, geo in self.geometries.items() if key != 'geometrycollection']:
  218. self.assertFalse(GeometryForm(data={'g': invalid.wkt}).is_valid())
  219. @skipUnless(HAS_GDAL, "OSMWidgetTest needs GDAL support")
  220. @skipUnlessDBFeature("gis_enabled")
  221. class OSMWidgetTest(SimpleTestCase):
  222. def setUp(self):
  223. self.geometries = {
  224. 'point': GEOSGeometry("SRID=4326;POINT(9.052734375 42.451171875)"),
  225. }
  226. def test_osm_widget(self):
  227. class PointForm(forms.Form):
  228. p = forms.PointField(widget=forms.OSMWidget)
  229. geom = self.geometries['point']
  230. form = PointForm(data={'p': geom})
  231. rendered = form.as_p()
  232. self.assertIn("OpenStreetMap (Mapnik)", rendered)
  233. self.assertIn("id: 'id_p',", rendered)
  234. def test_default_lat_lon(self):
  235. class PointForm(forms.Form):
  236. p = forms.PointField(
  237. widget=forms.OSMWidget(attrs={
  238. 'default_lon': 20, 'default_lat': 30
  239. }),
  240. )
  241. form = PointForm()
  242. rendered = form.as_p()
  243. self.assertIn("options['default_lon'] = 20;", rendered)
  244. self.assertIn("options['default_lat'] = 30;", rendered)
  245. if forms.OSMWidget.default_lon != 20:
  246. self.assertNotIn(
  247. "options['default_lon'] = %d;" % forms.OSMWidget.default_lon,
  248. rendered)
  249. if forms.OSMWidget.default_lat != 30:
  250. self.assertNotIn(
  251. "options['default_lat'] = %d;" % forms.OSMWidget.default_lat,
  252. rendered)
  253. @skipUnless(HAS_GDAL, "CustomGeometryWidgetTest needs GDAL support")
  254. @skipUnlessDBFeature("gis_enabled")
  255. class CustomGeometryWidgetTest(SimpleTestCase):
  256. def test_custom_serialization_widget(self):
  257. class CustomGeometryWidget(forms.BaseGeometryWidget):
  258. template_name = 'gis/openlayers.html'
  259. deserialize_called = 0
  260. def serialize(self, value):
  261. return value.json if value else ''
  262. def deserialize(self, value):
  263. self.deserialize_called += 1
  264. return GEOSGeometry(value)
  265. class PointForm(forms.Form):
  266. p = forms.PointField(widget=CustomGeometryWidget)
  267. point = GEOSGeometry("SRID=4326;POINT(9.052734375 42.451171875)")
  268. form = PointForm(data={'p': point})
  269. self.assertIn(escape(point.json), form.as_p())
  270. CustomGeometryWidget.called = 0
  271. widget = form.fields['p'].widget
  272. # Force deserialize use due to a string value
  273. self.assertIn(escape(point.json), widget.render('p', point.json))
  274. self.assertEqual(widget.deserialize_called, 1)
  275. form = PointForm(data={'p': point.json})
  276. self.assertTrue(form.is_valid())
  277. # Ensure that resulting geometry has srid set
  278. self.assertEqual(form.cleaned_data['p'].srid, 4326)