test_geoforms.py 16 KB

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