Skip to content

Commit

Permalink
Merge pull request #34849 from dimagi/ze/validate-unique-saved-poly-n…
Browse files Browse the repository at this point in the history
…ames

Validate Unique Saved GeoPolygon Names (Geospatial)
  • Loading branch information
zandre-eng authored Jul 3, 2024
2 parents 8232c9d + 114b4d9 commit b5da1f2
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 20 deletions.
27 changes: 19 additions & 8 deletions corehq/apps/geospatial/static/geospatial/js/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -771,12 +771,8 @@ hqDefine('geospatial/js/models', [
self.saveGeoPolygon = function () {
let data = self.mapObj.drawControls.getAll();
if (data.features.length) {
let name = window.prompt(gettext("Name of the Area"));
if (name === null) {
return;
}
if (name === '') {
alertUser.alert_user(gettext("Please enter the name for the area!"), 'warning', false, true);
const name = window.prompt(gettext("Name of the Area"));
if (!validateSavedPolygonName(name)) {
return;
}
data['name'] = name;
Expand All @@ -802,13 +798,28 @@ hqDefine('geospatial/js/models', [
self.selectedSavedPolygonId(ret.id);
self.shouldRefreshPage(true);
},
error: function () {
alertUser.alert_user(gettext(unexpectedErrorMessage), 'danger');
error: function (response) {
const responseText = response.responseText;
if (responseText) {
alertUser.alert_user(gettext(responseText), 'danger');
} else {
alertUser.alert_user(gettext(unexpectedErrorMessage), 'danger');
}
},
});
}
};

function validateSavedPolygonName(name) {
if (name === null) {
return false;
}
if (name === '') {
alertUser.alert_user(gettext("Please enter the name for the area!"), 'warning', false, true);
return false;
}
return true;
}
};

return {
Expand Down
55 changes: 47 additions & 8 deletions corehq/apps/geospatial/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,23 +424,26 @@ def tearDown(self):
GeoPolygon.objects.all().delete()
super().tearDown()

def _make_post_request(self, data):
return self.client.post(
self.endpoint,
data=data,
content_type='application/json',
)

@flag_enabled('GEOSPATIAL')
def test_not_logged_in(self):
response = Client().post(self.endpoint, _sample_geojson_data())
self.assertRedirects(response, f"{self.login_url}?next={self.endpoint}")

def test_feature_flag_not_enabled(self):
response = self.client.post(self.endpoint, _sample_geojson_data())
response = self._make_post_request({'geo_json': _sample_geojson_data()})
self.assertTrue(response.status_code == 404)

@flag_enabled('GEOSPATIAL')
def test_save_polygon(self):
geo_json_data = _sample_geojson_data()
response = self.client.post(
self.endpoint,
data={"geo_json": geo_json_data},
content_type="application/json"
)
response = self._make_post_request({'geo_json': geo_json_data})
self.assertEqual(response.status_code, 200)
saved_polygons = GeoPolygon.objects.filter(domain=self.domain)
self.assertEqual(len(saved_polygons), 1)
Expand All @@ -450,6 +453,42 @@ def test_save_polygon(self):
del feature['id']
self.assertEqual(saved_polygons[0].geo_json, geo_json_data)

def _assert_error_message(self, response, message):
error = response.content.decode("utf-8")
self.assertEqual(response.status_code, 400)
self.assertEqual(error, message,)

@flag_enabled('GEOSPATIAL')
def test_geo_json_validation(self):
response = self.client.generic('POST', self.endpoint, 'foobar')
self._assert_error_message(
response,
message='POST Body must be a valid json in {"geo_json": <geo_json>} format'
)
response = self._make_post_request({'foo': 'bar'})
self._assert_error_message(
response,
message='Empty geo_json POST field'
)
response = self._make_post_request({'geo_json': {'foo': 'bar'}})
self._assert_error_message(
response,
message='Invalid GeoJSON, geo_json must be a FeatureCollection of Polygons'
)

@flag_enabled('GEOSPATIAL')
def test_name_validation(self):
GeoPolygon.objects.create(
domain=self.domain,
geo_json={},
name='foobar',
)
response = self._make_post_request({'geo_json': _sample_geojson_data(name='FooBAR')})
self._assert_error_message(
response,
message='GeoPolygon with given name already exists! Please use a different name.'
)


class TestGeoPolygonDetailView(BaseGeospatialViewClass):
urlname = GeoPolygonDetailView.urlname
Expand Down Expand Up @@ -502,7 +541,7 @@ def test_delete_polygon(self):
self.assertEqual(len(saved_polygons), 0)


def _sample_geojson_data():
def _sample_geojson_data(name='test-2'):
data = {
"type": "FeatureCollection",
"features": [
Expand All @@ -524,6 +563,6 @@ def _sample_geojson_data():
}
}
],
"name": "test-2",
"name": name,
}
return data
15 changes: 11 additions & 4 deletions corehq/apps/geospatial/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,25 +78,32 @@ def post(self, request, *args, **kwargs):
try:
geo_json = json.loads(request.body).get('geo_json', None)
except json.decoder.JSONDecodeError:
raise HttpResponseBadRequest(
return HttpResponseBadRequest(
'POST Body must be a valid json in {"geo_json": <geo_json>} format'
)

if not geo_json:
raise HttpResponseBadRequest('Empty geo_json POST field')
return HttpResponseBadRequest('Empty geo_json POST field')

try:
jsonschema.validate(geo_json, POLYGON_COLLECTION_GEOJSON_SCHEMA)
except jsonschema.exceptions.ValidationError:
raise HttpResponseBadRequest(
return HttpResponseBadRequest(
'Invalid GeoJSON, geo_json must be a FeatureCollection of Polygons'
)

geo_polygon_name = geo_json.pop('name')
if GeoPolygon.objects.filter(domain=self.domain, name__iexact=geo_polygon_name).exists():
return HttpResponseBadRequest(
'GeoPolygon with given name already exists! Please use a different name.'
)

# Drop ids since they are specific to the Mapbox draw event
for feature in geo_json["features"]:
del feature['id']

geo_polygon = GeoPolygon.objects.create(
name=geo_json.pop('name'),
name=geo_polygon_name,
domain=self.domain,
geo_json=geo_json
)
Expand Down

0 comments on commit b5da1f2

Please sign in to comment.