1. import re
    
  2. 
    
  3. from django.contrib.gis import forms
    
  4. from django.contrib.gis.forms import BaseGeometryWidget, OpenLayersWidget
    
  5. from django.contrib.gis.geos import GEOSGeometry
    
  6. from django.core.exceptions import ValidationError
    
  7. from django.test import SimpleTestCase, override_settings
    
  8. from django.utils.html import escape
    
  9. 
    
  10. 
    
  11. class GeometryFieldTest(SimpleTestCase):
    
  12.     def test_init(self):
    
  13.         "Testing GeometryField initialization with defaults."
    
  14.         fld = forms.GeometryField()
    
  15.         for bad_default in ("blah", 3, "FoO", None, 0):
    
  16.             with self.subTest(bad_default=bad_default):
    
  17.                 with self.assertRaises(ValidationError):
    
  18.                     fld.clean(bad_default)
    
  19. 
    
  20.     def test_srid(self):
    
  21.         "Testing GeometryField with a SRID set."
    
  22.         # Input that doesn't specify the SRID is assumed to be in the SRID
    
  23.         # of the input field.
    
  24.         fld = forms.GeometryField(srid=4326)
    
  25.         geom = fld.clean("POINT(5 23)")
    
  26.         self.assertEqual(4326, geom.srid)
    
  27.         # Making the field in a different SRID from that of the geometry, and
    
  28.         # asserting it transforms.
    
  29.         fld = forms.GeometryField(srid=32140)
    
  30.         # Different PROJ versions use different transformations, all are
    
  31.         # correct as having a 1 meter accuracy.
    
  32.         tol = 1
    
  33.         xform_geom = GEOSGeometry(
    
  34.             "POINT (951640.547328465 4219369.26171664)", srid=32140
    
  35.         )
    
  36.         # The cleaned geometry is transformed to 32140 (the widget map_srid is 3857).
    
  37.         cleaned_geom = fld.clean(
    
  38.             "SRID=3857;POINT (-10615777.40976205 3473169.895707852)"
    
  39.         )
    
  40.         self.assertEqual(cleaned_geom.srid, 32140)
    
  41.         self.assertTrue(xform_geom.equals_exact(cleaned_geom, tol))
    
  42. 
    
  43.     def test_null(self):
    
  44.         "Testing GeometryField's handling of null (None) geometries."
    
  45.         # Form fields, by default, are required (`required=True`)
    
  46.         fld = forms.GeometryField()
    
  47.         with self.assertRaisesMessage(ValidationError, "No geometry value provided."):
    
  48.             fld.clean(None)
    
  49. 
    
  50.         # This will clean None as a geometry (See #10660).
    
  51.         fld = forms.GeometryField(required=False)
    
  52.         self.assertIsNone(fld.clean(None))
    
  53. 
    
  54.     def test_geom_type(self):
    
  55.         "Testing GeometryField's handling of different geometry types."
    
  56.         # By default, all geometry types are allowed.
    
  57.         fld = forms.GeometryField()
    
  58.         for wkt in (
    
  59.             "POINT(5 23)",
    
  60.             "MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))",
    
  61.             "LINESTRING(0 0, 1 1)",
    
  62.         ):
    
  63.             with self.subTest(wkt=wkt):
    
  64.                 # to_python() uses the SRID of OpenLayersWidget if the
    
  65.                 # converted value doesn't have an SRID.
    
  66.                 self.assertEqual(
    
  67.                     GEOSGeometry(wkt, srid=fld.widget.map_srid), fld.clean(wkt)
    
  68.                 )
    
  69. 
    
  70.         pnt_fld = forms.GeometryField(geom_type="POINT")
    
  71.         self.assertEqual(
    
  72.             GEOSGeometry("POINT(5 23)", srid=pnt_fld.widget.map_srid),
    
  73.             pnt_fld.clean("POINT(5 23)"),
    
  74.         )
    
  75.         # a WKT for any other geom_type will be properly transformed by `to_python`
    
  76.         self.assertEqual(
    
  77.             GEOSGeometry("LINESTRING(0 0, 1 1)", srid=pnt_fld.widget.map_srid),
    
  78.             pnt_fld.to_python("LINESTRING(0 0, 1 1)"),
    
  79.         )
    
  80.         # but rejected by `clean`
    
  81.         with self.assertRaises(ValidationError):
    
  82.             pnt_fld.clean("LINESTRING(0 0, 1 1)")
    
  83. 
    
  84.     def test_to_python(self):
    
  85.         """
    
  86.         to_python() either returns a correct GEOSGeometry object or
    
  87.         a ValidationError.
    
  88.         """
    
  89.         good_inputs = [
    
  90.             "POINT(5 23)",
    
  91.             "MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))",
    
  92.             "LINESTRING(0 0, 1 1)",
    
  93.         ]
    
  94.         bad_inputs = [
    
  95.             "POINT(5)",
    
  96.             "MULTI   POLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))",
    
  97.             "BLAH(0 0, 1 1)",
    
  98.             '{"type": "FeatureCollection", "features": ['
    
  99.             '{"geometry": {"type": "Point", "coordinates": [508375, 148905]}, '
    
  100.             '"type": "Feature"}]}',
    
  101.         ]
    
  102.         fld = forms.GeometryField()
    
  103.         # to_python returns the same GEOSGeometry for a WKT
    
  104.         for geo_input in good_inputs:
    
  105.             with self.subTest(geo_input=geo_input):
    
  106.                 self.assertEqual(
    
  107.                     GEOSGeometry(geo_input, srid=fld.widget.map_srid),
    
  108.                     fld.to_python(geo_input),
    
  109.                 )
    
  110.         # but raises a ValidationError for any other string
    
  111.         for geo_input in bad_inputs:
    
  112.             with self.subTest(geo_input=geo_input):
    
  113.                 with self.assertRaises(ValidationError):
    
  114.                     fld.to_python(geo_input)
    
  115. 
    
  116.     def test_to_python_different_map_srid(self):
    
  117.         f = forms.GeometryField(widget=OpenLayersWidget)
    
  118.         json = '{ "type": "Point", "coordinates": [ 5.0, 23.0 ] }'
    
  119.         self.assertEqual(
    
  120.             GEOSGeometry("POINT(5 23)", srid=f.widget.map_srid), f.to_python(json)
    
  121.         )
    
  122. 
    
  123.     def test_field_with_text_widget(self):
    
  124.         class PointForm(forms.Form):
    
  125.             pt = forms.PointField(srid=4326, widget=forms.TextInput)
    
  126. 
    
  127.         form = PointForm()
    
  128.         cleaned_pt = form.fields["pt"].clean("POINT(5 23)")
    
  129.         self.assertEqual(cleaned_pt, GEOSGeometry("POINT(5 23)", srid=4326))
    
  130.         self.assertEqual(4326, cleaned_pt.srid)
    
  131.         with self.assertRaisesMessage(ValidationError, "Invalid geometry value."):
    
  132.             form.fields["pt"].clean("POINT(5)")
    
  133. 
    
  134.         point = GEOSGeometry("SRID=4326;POINT(5 23)")
    
  135.         form = PointForm(data={"pt": "POINT(5 23)"}, initial={"pt": point})
    
  136.         self.assertFalse(form.has_changed())
    
  137. 
    
  138.     def test_field_string_value(self):
    
  139.         """
    
  140.         Initialization of a geometry field with a valid/empty/invalid string.
    
  141.         Only the invalid string should trigger an error log entry.
    
  142.         """
    
  143. 
    
  144.         class PointForm(forms.Form):
    
  145.             pt1 = forms.PointField(srid=4326)
    
  146.             pt2 = forms.PointField(srid=4326)
    
  147.             pt3 = forms.PointField(srid=4326)
    
  148. 
    
  149.         form = PointForm(
    
  150.             {
    
  151.                 "pt1": "SRID=4326;POINT(7.3 44)",  # valid
    
  152.                 "pt2": "",  # empty
    
  153.                 "pt3": "PNT(0)",  # invalid
    
  154.             }
    
  155.         )
    
  156. 
    
  157.         with self.assertLogs("django.contrib.gis", "ERROR") as logger_calls:
    
  158.             output = str(form)
    
  159. 
    
  160.         # The first point can't use assertInHTML() due to non-deterministic
    
  161.         # ordering of the rendered dictionary.
    
  162.         pt1_serialized = re.search(r"<textarea [^>]*>({[^<]+})<", output)[1]
    
  163.         pt1_json = pt1_serialized.replace("&quot;", '"')
    
  164.         pt1_expected = GEOSGeometry(form.data["pt1"]).transform(3857, clone=True)
    
  165.         self.assertJSONEqual(pt1_json, pt1_expected.json)
    
  166. 
    
  167.         self.assertInHTML(
    
  168.             '<textarea id="id_pt2" class="vSerializedField required" cols="150"'
    
  169.             ' rows="10" name="pt2"></textarea>',
    
  170.             output,
    
  171.         )
    
  172.         self.assertInHTML(
    
  173.             '<textarea id="id_pt3" class="vSerializedField required" cols="150"'
    
  174.             ' rows="10" name="pt3"></textarea>',
    
  175.             output,
    
  176.         )
    
  177.         # Only the invalid PNT(0) triggers an error log entry.
    
  178.         # Deserialization is called in form clean and in widget rendering.
    
  179.         self.assertEqual(len(logger_calls.records), 2)
    
  180.         self.assertEqual(
    
  181.             logger_calls.records[0].getMessage(),
    
  182.             "Error creating geometry from value 'PNT(0)' (String input "
    
  183.             "unrecognized as WKT EWKT, and HEXEWKB.)",
    
  184.         )
    
  185. 
    
  186. 
    
  187. class SpecializedFieldTest(SimpleTestCase):
    
  188.     def setUp(self):
    
  189.         self.geometries = {
    
  190.             "point": GEOSGeometry("SRID=4326;POINT(9.052734375 42.451171875)"),
    
  191.             "multipoint": GEOSGeometry(
    
  192.                 "SRID=4326;MULTIPOINT("
    
  193.                 "(13.18634033203125 14.504356384277344),"
    
  194.                 "(13.207969665527 14.490966796875),"
    
  195.                 "(13.177070617675 14.454917907714))"
    
  196.             ),
    
  197.             "linestring": GEOSGeometry(
    
  198.                 "SRID=4326;LINESTRING("
    
  199.                 "-8.26171875 -0.52734375,"
    
  200.                 "-7.734375 4.21875,"
    
  201.                 "6.85546875 3.779296875,"
    
  202.                 "5.44921875 -3.515625)"
    
  203.             ),
    
  204.             "multilinestring": GEOSGeometry(
    
  205.                 "SRID=4326;MULTILINESTRING("
    
  206.                 "(-16.435546875 -2.98828125,"
    
  207.                 "-17.2265625 2.98828125,"
    
  208.                 "-0.703125 3.515625,"
    
  209.                 "-1.494140625 -3.33984375),"
    
  210.                 "(-8.0859375 -5.9765625,"
    
  211.                 "8.525390625 -8.7890625,"
    
  212.                 "12.392578125 -0.87890625,"
    
  213.                 "10.01953125 7.646484375))"
    
  214.             ),
    
  215.             "polygon": GEOSGeometry(
    
  216.                 "SRID=4326;POLYGON("
    
  217.                 "(-1.669921875 6.240234375,"
    
  218.                 "-3.8671875 -0.615234375,"
    
  219.                 "5.9765625 -3.955078125,"
    
  220.                 "18.193359375 3.955078125,"
    
  221.                 "9.84375 9.4921875,"
    
  222.                 "-1.669921875 6.240234375))"
    
  223.             ),
    
  224.             "multipolygon": GEOSGeometry(
    
  225.                 "SRID=4326;MULTIPOLYGON("
    
  226.                 "((-17.578125 13.095703125,"
    
  227.                 "-17.2265625 10.8984375,"
    
  228.                 "-13.974609375 10.1953125,"
    
  229.                 "-13.359375 12.744140625,"
    
  230.                 "-15.732421875 13.7109375,"
    
  231.                 "-17.578125 13.095703125)),"
    
  232.                 "((-8.525390625 5.537109375,"
    
  233.                 "-8.876953125 2.548828125,"
    
  234.                 "-5.888671875 1.93359375,"
    
  235.                 "-5.09765625 4.21875,"
    
  236.                 "-6.064453125 6.240234375,"
    
  237.                 "-8.525390625 5.537109375)))"
    
  238.             ),
    
  239.             "geometrycollection": GEOSGeometry(
    
  240.                 "SRID=4326;GEOMETRYCOLLECTION("
    
  241.                 "POINT(5.625 -0.263671875),"
    
  242.                 "POINT(6.767578125 -3.603515625),"
    
  243.                 "POINT(8.525390625 0.087890625),"
    
  244.                 "POINT(8.0859375 -2.13134765625),"
    
  245.                 "LINESTRING("
    
  246.                 "6.273193359375 -1.175537109375,"
    
  247.                 "5.77880859375 -1.812744140625,"
    
  248.                 "7.27294921875 -2.230224609375,"
    
  249.                 "7.657470703125 -1.25244140625))"
    
  250.             ),
    
  251.         }
    
  252. 
    
  253.     def assertMapWidget(self, form_instance):
    
  254.         """
    
  255.         Make sure the MapWidget js is passed in the form media and a MapWidget
    
  256.         is actually created
    
  257.         """
    
  258.         self.assertTrue(form_instance.is_valid())
    
  259.         rendered = form_instance.as_p()
    
  260.         self.assertIn("new MapWidget(options);", rendered)
    
  261.         self.assertIn("map_srid: 3857,", rendered)
    
  262.         self.assertIn("gis/js/OLMapWidget.js", str(form_instance.media))
    
  263. 
    
  264.     def assertTextarea(self, geom, rendered):
    
  265.         """Makes sure the wkt and a textarea are in the content"""
    
  266. 
    
  267.         self.assertIn("<textarea ", rendered)
    
  268.         self.assertIn("required", rendered)
    
  269.         ogr = geom.ogr
    
  270.         ogr.transform(3857)
    
  271.         self.assertIn(escape(ogr.json), rendered)
    
  272. 
    
  273.     # map_srid in openlayers.html template must not be localized.
    
  274.     @override_settings(USE_THOUSAND_SEPARATOR=True)
    
  275.     def test_pointfield(self):
    
  276.         class PointForm(forms.Form):
    
  277.             p = forms.PointField()
    
  278. 
    
  279.         geom = self.geometries["point"]
    
  280.         form = PointForm(data={"p": geom})
    
  281.         self.assertTextarea(geom, form.as_p())
    
  282.         self.assertMapWidget(form)
    
  283.         self.assertFalse(PointForm().is_valid())
    
  284.         invalid = PointForm(data={"p": "some invalid geom"})
    
  285.         self.assertFalse(invalid.is_valid())
    
  286.         self.assertIn("Invalid geometry value", str(invalid.errors))
    
  287. 
    
  288.         for invalid in [geo for key, geo in self.geometries.items() if key != "point"]:
    
  289.             self.assertFalse(PointForm(data={"p": invalid.wkt}).is_valid())
    
  290. 
    
  291.     def test_multipointfield(self):
    
  292.         class PointForm(forms.Form):
    
  293.             p = forms.MultiPointField()
    
  294. 
    
  295.         geom = self.geometries["multipoint"]
    
  296.         form = PointForm(data={"p": geom})
    
  297.         self.assertTextarea(geom, form.as_p())
    
  298.         self.assertMapWidget(form)
    
  299.         self.assertFalse(PointForm().is_valid())
    
  300. 
    
  301.         for invalid in [
    
  302.             geo for key, geo in self.geometries.items() if key != "multipoint"
    
  303.         ]:
    
  304.             self.assertFalse(PointForm(data={"p": invalid.wkt}).is_valid())
    
  305. 
    
  306.     def test_linestringfield(self):
    
  307.         class LineStringForm(forms.Form):
    
  308.             f = forms.LineStringField()
    
  309. 
    
  310.         geom = self.geometries["linestring"]
    
  311.         form = LineStringForm(data={"f": geom})
    
  312.         self.assertTextarea(geom, form.as_p())
    
  313.         self.assertMapWidget(form)
    
  314.         self.assertFalse(LineStringForm().is_valid())
    
  315. 
    
  316.         for invalid in [
    
  317.             geo for key, geo in self.geometries.items() if key != "linestring"
    
  318.         ]:
    
  319.             self.assertFalse(LineStringForm(data={"p": invalid.wkt}).is_valid())
    
  320. 
    
  321.     def test_multilinestringfield(self):
    
  322.         class LineStringForm(forms.Form):
    
  323.             f = forms.MultiLineStringField()
    
  324. 
    
  325.         geom = self.geometries["multilinestring"]
    
  326.         form = LineStringForm(data={"f": geom})
    
  327.         self.assertTextarea(geom, form.as_p())
    
  328.         self.assertMapWidget(form)
    
  329.         self.assertFalse(LineStringForm().is_valid())
    
  330. 
    
  331.         for invalid in [
    
  332.             geo for key, geo in self.geometries.items() if key != "multilinestring"
    
  333.         ]:
    
  334.             self.assertFalse(LineStringForm(data={"p": invalid.wkt}).is_valid())
    
  335. 
    
  336.     def test_polygonfield(self):
    
  337.         class PolygonForm(forms.Form):
    
  338.             p = forms.PolygonField()
    
  339. 
    
  340.         geom = self.geometries["polygon"]
    
  341.         form = PolygonForm(data={"p": geom})
    
  342.         self.assertTextarea(geom, form.as_p())
    
  343.         self.assertMapWidget(form)
    
  344.         self.assertFalse(PolygonForm().is_valid())
    
  345. 
    
  346.         for invalid in [
    
  347.             geo for key, geo in self.geometries.items() if key != "polygon"
    
  348.         ]:
    
  349.             self.assertFalse(PolygonForm(data={"p": invalid.wkt}).is_valid())
    
  350. 
    
  351.     def test_multipolygonfield(self):
    
  352.         class PolygonForm(forms.Form):
    
  353.             p = forms.MultiPolygonField()
    
  354. 
    
  355.         geom = self.geometries["multipolygon"]
    
  356.         form = PolygonForm(data={"p": geom})
    
  357.         self.assertTextarea(geom, form.as_p())
    
  358.         self.assertMapWidget(form)
    
  359.         self.assertFalse(PolygonForm().is_valid())
    
  360. 
    
  361.         for invalid in [
    
  362.             geo for key, geo in self.geometries.items() if key != "multipolygon"
    
  363.         ]:
    
  364.             self.assertFalse(PolygonForm(data={"p": invalid.wkt}).is_valid())
    
  365. 
    
  366.     def test_geometrycollectionfield(self):
    
  367.         class GeometryForm(forms.Form):
    
  368.             g = forms.GeometryCollectionField()
    
  369. 
    
  370.         geom = self.geometries["geometrycollection"]
    
  371.         form = GeometryForm(data={"g": geom})
    
  372.         self.assertTextarea(geom, form.as_p())
    
  373.         self.assertMapWidget(form)
    
  374.         self.assertFalse(GeometryForm().is_valid())
    
  375. 
    
  376.         for invalid in [
    
  377.             geo for key, geo in self.geometries.items() if key != "geometrycollection"
    
  378.         ]:
    
  379.             self.assertFalse(GeometryForm(data={"g": invalid.wkt}).is_valid())
    
  380. 
    
  381. 
    
  382. class OSMWidgetTest(SimpleTestCase):
    
  383.     def setUp(self):
    
  384.         self.geometries = {
    
  385.             "point": GEOSGeometry("SRID=4326;POINT(9.052734375 42.451171875)"),
    
  386.         }
    
  387. 
    
  388.     def test_osm_widget(self):
    
  389.         class PointForm(forms.Form):
    
  390.             p = forms.PointField(widget=forms.OSMWidget)
    
  391. 
    
  392.         geom = self.geometries["point"]
    
  393.         form = PointForm(data={"p": geom})
    
  394.         rendered = form.as_p()
    
  395. 
    
  396.         self.assertIn("ol.source.OSM()", rendered)
    
  397.         self.assertIn("id: 'id_p',", rendered)
    
  398. 
    
  399.     def test_default_lat_lon(self):
    
  400.         self.assertEqual(forms.OSMWidget.default_lon, 5)
    
  401.         self.assertEqual(forms.OSMWidget.default_lat, 47)
    
  402.         self.assertEqual(forms.OSMWidget.default_zoom, 12)
    
  403. 
    
  404.         class PointForm(forms.Form):
    
  405.             p = forms.PointField(
    
  406.                 widget=forms.OSMWidget(
    
  407.                     attrs={
    
  408.                         "default_lon": 20,
    
  409.                         "default_lat": 30,
    
  410.                         "default_zoom": 17,
    
  411.                     }
    
  412.                 ),
    
  413.             )
    
  414. 
    
  415.         form = PointForm()
    
  416.         rendered = form.as_p()
    
  417. 
    
  418.         self.assertIn("options['default_lon'] = 20;", rendered)
    
  419.         self.assertIn("options['default_lat'] = 30;", rendered)
    
  420.         self.assertIn("options['default_zoom'] = 17;", rendered)
    
  421. 
    
  422. 
    
  423. class GeometryWidgetTests(SimpleTestCase):
    
  424.     def test_get_context_attrs(self):
    
  425.         # The Widget.get_context() attrs argument overrides self.attrs.
    
  426.         widget = BaseGeometryWidget(attrs={"geom_type": "POINT"})
    
  427.         context = widget.get_context("point", None, attrs={"geom_type": "POINT2"})
    
  428.         self.assertEqual(context["geom_type"], "POINT2")
    
  429.         # Widget.get_context() returns expected name for geom_type.
    
  430.         widget = BaseGeometryWidget(attrs={"geom_type": "POLYGON"})
    
  431.         context = widget.get_context("polygon", None, None)
    
  432.         self.assertEqual(context["geom_type"], "Polygon")
    
  433.         # Widget.get_context() returns 'Geometry' instead of 'Unknown'.
    
  434.         widget = BaseGeometryWidget(attrs={"geom_type": "GEOMETRY"})
    
  435.         context = widget.get_context("geometry", None, None)
    
  436.         self.assertEqual(context["geom_type"], "Geometry")
    
  437. 
    
  438.     def test_subwidgets(self):
    
  439.         widget = forms.BaseGeometryWidget()
    
  440.         self.assertEqual(
    
  441.             list(widget.subwidgets("name", "value")),
    
  442.             [
    
  443.                 {
    
  444.                     "is_hidden": False,
    
  445.                     "attrs": {
    
  446.                         "map_srid": 4326,
    
  447.                         "map_width": 600,
    
  448.                         "geom_type": "GEOMETRY",
    
  449.                         "map_height": 400,
    
  450.                         "display_raw": False,
    
  451.                     },
    
  452.                     "name": "name",
    
  453.                     "template_name": "",
    
  454.                     "value": "value",
    
  455.                     "required": False,
    
  456.                 }
    
  457.             ],
    
  458.         )
    
  459. 
    
  460.     def test_custom_serialization_widget(self):
    
  461.         class CustomGeometryWidget(forms.BaseGeometryWidget):
    
  462.             template_name = "gis/openlayers.html"
    
  463.             deserialize_called = 0
    
  464. 
    
  465.             def serialize(self, value):
    
  466.                 return value.json if value else ""
    
  467. 
    
  468.             def deserialize(self, value):
    
  469.                 self.deserialize_called += 1
    
  470.                 return GEOSGeometry(value)
    
  471. 
    
  472.         class PointForm(forms.Form):
    
  473.             p = forms.PointField(widget=CustomGeometryWidget)
    
  474. 
    
  475.         point = GEOSGeometry("SRID=4326;POINT(9.052734375 42.451171875)")
    
  476.         form = PointForm(data={"p": point})
    
  477.         self.assertIn(escape(point.json), form.as_p())
    
  478. 
    
  479.         CustomGeometryWidget.called = 0
    
  480.         widget = form.fields["p"].widget
    
  481.         # Force deserialize use due to a string value
    
  482.         self.assertIn(escape(point.json), widget.render("p", point.json))
    
  483.         self.assertEqual(widget.deserialize_called, 1)
    
  484. 
    
  485.         form = PointForm(data={"p": point.json})
    
  486.         self.assertTrue(form.is_valid())
    
  487.         self.assertEqual(form.cleaned_data["p"].srid, 4326)