From 02cee914131697fc1c7e2b82de96cf3403fb7da6 Mon Sep 17 00:00:00 2001 From: Walter Lorenzetti Date: Mon, 12 Feb 2024 10:49:23 +0100 Subject: [PATCH] Check for 'field', 'suggest' and 'ordering' API parameters in HTTP POST calls. (#748) (#750) Co-authored-by: wlorenzetti (cherry picked from commit b94e09b51b85332c9917ed12cb0b481e2226c9fd) --- g3w-admin/core/api/filters.py | 27 ++++++-- g3w-admin/qdjango/tests/test_api.py | 95 ++++++++++++++++++++++++++++- 2 files changed, 115 insertions(+), 7 deletions(-) diff --git a/g3w-admin/core/api/filters.py b/g3w-admin/core/api/filters.py index 422cdb29d..960650e81 100644 --- a/g3w-admin/core/api/filters.py +++ b/g3w-admin/core/api/filters.py @@ -81,11 +81,18 @@ def apply_filter(self, request, metadata_layer, qgis_feature_request, view): qgis_layer = metadata_layer.qgis_layer - if request.query_params.get('search'): + # Try to get param from GET + search_value = request.query_params.get('search') + + if not search_value: + # Try to get from POST + search_value = request.data.get('search') + + if search_value: search_parts = [] - for search_term in request.query_params.get('search').split(','): + for search_term in search_value.split(','): search_term = self._quote_value('%' + search_term + '%') exp_template = '{field_name} ILIKE ' + search_term @@ -114,11 +121,18 @@ def apply_filter(self, request, metadata_layer, qgis_feature_request, view): qgis_layer = metadata_layer.qgis_layer - if request.query_params.get('ordering') is not None: + # Try to get param from GET + ordering_value = request.query_params.get('ordering') + + if not ordering_value: + # Try to get from POST + ordering_value = request.data.get('ordering') + + if ordering_value is not None: ordering_rules = [] - for ordering in request.query_params.get('ordering').split(','): + for ordering in ordering_value.split(','): ascending = True if ordering.startswith('-'): ordering = ordering[1:] @@ -215,8 +229,13 @@ def apply_filter(self, request, metadata_layer, qgis_feature_request, view): qgis_layer = metadata_layer.qgis_layer + # Try to get param from GET suggest_value = request.query_params.get('suggest') + if not suggest_value: + # Try to get from POST + suggest_value = request.data.get('suggest') + if suggest_value: # get field and value diff --git a/g3w-admin/qdjango/tests/test_api.py b/g3w-admin/qdjango/tests/test_api.py index 52dde889e..a4d59eb91 100644 --- a/g3w-admin/qdjango/tests/test_api.py +++ b/g3w-admin/qdjango/tests/test_api.py @@ -651,11 +651,11 @@ def tearDownClass(cls): cls.project322.instance.delete() super().tearDownClass() - def _testApiCall(self, view_name, args, kwargs={}, status_auth=200, login=True, logout=True): + def _testApiCall(self, view_name, args, kwargs={}, status_auth=200, login=True, logout=True, method='get'): """Utility to make test calls for admin01 user""" path = reverse(view_name, args=args) - if kwargs: + if kwargs and method == 'get': path += '?' parts = [] for k, v in kwargs.items(): @@ -671,7 +671,8 @@ def _testApiCall(self, view_name, args, kwargs={}, status_auth=200, login=True, if login: self.assertTrue(self.client.login( username='admin01', password='admin01')) - response = self.client.get(path) + + response = getattr(self.client, method)(path, data=kwargs) self.assertEqual(response.status_code, status_auth) if logout: self.client.logout() @@ -1304,6 +1305,7 @@ def test_server_filters_combination_api(self): qgs_request.setFilterExpression('"ISO2_CODE" = \'IT\'') total_count = len([f for f in qgis_layer.getFeatures(qgs_request)]) + # Test http 'get' method: resp = json.loads(self._testApiCall('core-vector-api', ['data', 'qdjango', self.project310.instance.pk, cities.qgs_layer_id], @@ -1313,6 +1315,17 @@ def test_server_filters_combination_api(self): self.assertEqual(resp['vector']['count'], total_count) + # Test http 'post' method: + resp = json.loads(self._testApiCall('core-vector-api', + ['data', 'qdjango', self.project310.instance.pk, + cities.qgs_layer_id], + { + 'field': 'ISO2_CODE|eq|IT' + }, + method='post').content) + + self.assertEqual(resp['vector']['count'], total_count) + qgs_request = QgsFeatureRequest() qgs_request.setFilterExpression( '"ISO2_CODE" = \'IT\' OR "ISO2_CODE" = \'FR\'') @@ -1332,6 +1345,7 @@ def test_server_filters_combination_api(self): '"ISO2_CODE" = \'IT\' AND "POPULATION" > 10000 OR "ISO2_CODE" = \'FR\'') total_count = len([f for f in qgis_layer.getFeatures(qgs_request)]) + # Test http 'get' method: resp = json.loads(self._testApiCall('core-vector-api', ['data', 'qdjango', self.project310.instance.pk, cities.qgs_layer_id], @@ -1341,6 +1355,17 @@ def test_server_filters_combination_api(self): self.assertEqual(resp['vector']['count'], total_count) + # Test http 'post' method: + resp = json.loads(self._testApiCall('core-vector-api', + ['data', 'qdjango', self.project310.instance.pk, + cities.qgs_layer_id], + { + 'field': 'ISO2_CODE|eq|IT|AND,POPULATION|gt|10000|OR,ISO2_CODE|eq|FR', + }, + method='post').content) + + self.assertEqual(resp['vector']['count'], total_count) + qgs_request = QgsFeatureRequest() qgs_request.setFilterExpression('"NAME" LIKE \'%Flo%\'') total_count = len([f for f in qgis_layer.getFeatures(qgs_request)]) @@ -1358,6 +1383,7 @@ def test_server_filters_combination_api(self): qgs_request.setFilterExpression('"NAME" ILIKE \'%flo%\'') total_count = len([f for f in qgis_layer.getFeatures(qgs_request)]) + # Test http 'get' method: resp = json.loads(self._testApiCall('core-vector-api', ['data', 'qdjango', self.project310.instance.pk, cities.qgs_layer_id], @@ -1367,11 +1393,23 @@ def test_server_filters_combination_api(self): self.assertEqual(resp['vector']['count'], total_count) + # Test http 'post' method: + resp = json.loads(self._testApiCall('core-vector-api', + ['data', 'qdjango', self.project310.instance.pk, + cities.qgs_layer_id], + { + 'field': 'NAME|ilike|flo' + }, + method='post').content) + + self.assertEqual(resp['vector']['count'], total_count) + qgs_request = QgsFeatureRequest() qgs_request.setFilterExpression( '"ISO2_CODE" = \'IT\' AND "NAME" = \'Florence\'') total_count = len([f for f in qgis_layer.getFeatures(qgs_request)]) + # Test http 'get' method: resp = json.loads(self._testApiCall('core-vector-api', ['data', 'qdjango', self.project310.instance.pk, cities.qgs_layer_id], @@ -1381,12 +1419,24 @@ def test_server_filters_combination_api(self): self.assertEqual(resp['vector']['count'], total_count) + # Test http 'post' method: + resp = json.loads(self._testApiCall('core-vector-api', + ['data', 'qdjango', self.project310.instance.pk, + cities.qgs_layer_id], + { + 'field': 'ISO2_CODE|eq|IT,NAME|eq|Florence' + }, + method='post').content) + + self.assertEqual(resp['vector']['count'], total_count) + # check SuggestFilterBackend # -------------------------- qgs_request = QgsFeatureRequest() qgs_request.setFilterExpression('"NAME" ILIKE \'%flo%\'') total_count = len([f for f in qgis_layer.getFeatures(qgs_request)]) + # Test http 'get' method: resp = json.loads(self._testApiCall('core-vector-api', ['data', 'qdjango', self.project310.instance.pk, cities.qgs_layer_id], @@ -1396,6 +1446,17 @@ def test_server_filters_combination_api(self): self.assertEqual(resp['vector']['count'], total_count) + # Test http 'post' method: + resp = json.loads(self._testApiCall('core-vector-api', + ['data', 'qdjango', self.project310.instance.pk, + cities.qgs_layer_id], + { + 'suggest': 'NAME|flo' + }, + method='post').content) + + self.assertEqual(resp['vector']['count'], total_count) + # check SuggestFilterBackend + FieldFilterBackend # ----------------------------------------------- qgs_request = QgsFeatureRequest() @@ -1403,6 +1464,7 @@ def test_server_filters_combination_api(self): '"NAME" ILIKE \'%flo%\' AND "ISO2_CODE" = \'IT\'') total_count = len([f for f in qgis_layer.getFeatures(qgs_request)]) + # Test http 'get' method: resp = json.loads(self._testApiCall('core-vector-api', ['data', 'qdjango', self.project310.instance.pk, cities.qgs_layer_id], @@ -1414,11 +1476,25 @@ def test_server_filters_combination_api(self): self.assertEqual(resp['vector']['count'], total_count) self.assertEqual(resp['vector']['count'], 2) + # Test http 'post' method: + resp = json.loads(self._testApiCall('core-vector-api', + ['data', 'qdjango', self.project310.instance.pk, + cities.qgs_layer_id], + { + 'suggest': 'NAME|flo', + 'field': 'ISO2_CODE|eq|IT' + }, + method='post').content) + + self.assertEqual(resp['vector']['count'], total_count) + self.assertEqual(resp['vector']['count'], 2) + qgs_request = QgsFeatureRequest() qgs_request.setFilterExpression( '"NAME" ILIKE \'%flo%\' AND "ISO2_CODE" = \'IT\' AND "NAME" = \'Florence\'') total_count = len([f for f in qgis_layer.getFeatures(qgs_request)]) + # Test http 'get' method: resp = json.loads(self._testApiCall('core-vector-api', ['data', 'qdjango', self.project310.instance.pk, cities.qgs_layer_id], @@ -1430,6 +1506,19 @@ def test_server_filters_combination_api(self): self.assertEqual(resp['vector']['count'], total_count) self.assertEqual(resp['vector']['count'], 1) + # Test http 'post' method: + resp = json.loads(self._testApiCall('core-vector-api', + ['data', 'qdjango', self.project310.instance.pk, + cities.qgs_layer_id], + { + 'suggest': 'NAME|flo', + 'field': 'ISO2_CODE|eq|IT,NAME|eq|Florence' + }, + method='post').content) + + self.assertEqual(resp['vector']['count'], total_count) + self.assertEqual(resp['vector']['count'], 1) + def test_unique_request_api_param(self): """ Test 'unique' url request param for 'data' vector API """