diff --git a/requirements-test.txt b/requirements-test.txt index e249bd33..d08e4023 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -4,3 +4,5 @@ contexttimer # QA checks openwisp-utils[qa]~=0.7.0 packaging~=20.4 +coreapi==2.3.3 +coreschema==0.0.4 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..c93ece9a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,16 @@ +autopep8==1.6.0 +certifi==2021.10.8 +distlib==0.3.4 +filelock==3.6.0 +packaging==21.3 +platformdirs==2.5.1 +pluggy==1.0.0 +py==1.11.0 +pycodestyle==2.8.0 +pyparsing==3.0.7 +six==1.16.0 +toml==0.10.2 +tox==3.24.5 +virtualenv==20.13.4 +coreapi==2.3.3 +coreschema==0.0.4 diff --git a/rest_framework_gis/filters.py b/rest_framework_gis/filters.py index 018241a4..a436a84b 100644 --- a/rest_framework_gis/filters.py +++ b/rest_framework_gis/filters.py @@ -1,4 +1,6 @@ from math import cos, pi +import coreapi +import coreschema from django.contrib.gis import forms from django.contrib.gis.db import models @@ -287,13 +289,73 @@ def filter_queryset(self, request, queryset, view): order = request.query_params.get(self.order_param) if order == 'desc': - return queryset.order_by(-GeometryDistance(filter_field, point)) + queryset = queryset.order_by(-GeometryDistance(filter_field, point)) else: - return queryset.order_by(GeometryDistance(filter_field, point)) + queryset = queryset.order_by(GeometryDistance(filter_field, point)) + + # distance in meters + dist_string = request.query_params.get(self.dist_param, 1000) + try: + dist = float(dist_string) + except ValueError: + raise ParseError( + "Invalid distance string supplied for parameter {}".format( + self.dist_param, + ), + ) + + convert_distance_input = getattr(view, "distance_filter_convert_meters", False) + if convert_distance_input: + # Warning: assumes that the point is (lon,lat) + dist = self.dist_to_deg(dist, point[1]) + geoDjango_filter = "dwithin" # use dwithin for points + return queryset.filter( + Q(**{f"{filter_field}__{geoDjango_filter}": (point, dist)}), + ) + + def get_schema_fields(self, view): + params = super().get_schema_fields(view) + params.extend( + [ + coreapi.Field( + name=self.point_param, + required=False, + location="query", + schema=coreschema.Array( + title="point parameter title", + description="Point represented in **longitude,latitude** format.", + items={"type": "float"}, + min_items=2, + max_items=2, + ), + ), + coreapi.Field( + name=self.dist_param, + required=False, + location="query", + schema=coreschema.Number( + title="distance parameter", + description="Distance from the point (in meters) to limit the search area.", + ), + ), + coreapi.Field( + name=self.order_param, + required=False, + location="query", + schema=coreschema.Enum( + enum=("asc", "desc"), + title="order direction", + description="The direction to sort results when comparing distance.", + ), + ), + ], + ) + + return params def get_schema_operation_parameters(self, view): params = super().get_schema_operation_parameters(view) - params.append( + params.extend([ { "name": self.order_param, "required": False, @@ -307,4 +369,6 @@ def get_schema_operation_parameters(self, view): "style": "form", "explode": False, } + ] ) + return params diff --git a/tests/django_restframework_gis_tests/test_filters.py b/tests/django_restframework_gis_tests/test_filters.py index 26a0d6ce..3855a900 100644 --- a/tests/django_restframework_gis_tests/test_filters.py +++ b/tests/django_restframework_gis_tests/test_filters.py @@ -356,7 +356,8 @@ def test_DistanceToPointFilter_filtering(self): def test_DistanceToPointOrderingFilter_filtering(self): """ Checks that the DistanceOrderingFilter returns the objects in the correct order - given the geometry defined by the URL parameters + given the geometry defined by the URL parameters. Also checks that the + DistanceToPointOrderingFilter can accept a dist parameter """ self.assertEqual(Location.objects.count(), 0) @@ -431,6 +432,15 @@ def test_DistanceToPointOrderingFilter_filtering(self): ], ) + # Test dist parameter + distance = 5 + url_params = '?point=%i,%i&dist=%i&format=json' % (point[0], point[1], distance) + response = self.client.get( + '%s%s' % (self.location_order_distance_to_point, url_params) + ) + self.assertEqual(len(response.data['features']), 1) + self.assertEqual(response.data['features'][0]['properties']['name'], 'Chicago') + @skipIf( has_spatialite, 'Skipped test for spatialite backend: missing feature "contains_properly"', diff --git a/tests/django_restframework_gis_tests/test_schema_generation.py b/tests/django_restframework_gis_tests/test_schema_generation.py index b9c45b5d..8bd37270 100644 --- a/tests/django_restframework_gis_tests/test_schema_generation.py +++ b/tests/django_restframework_gis_tests/test_schema_generation.py @@ -27,6 +27,7 @@ GeojsonLocationContainedInBBoxList, GeojsonLocationContainedInTileList, GeojsonLocationWithinDistanceOfPointList, + GeojsonLocationOrderDistanceToPointList, ModelViewWithPolygon, geojson_location_list, ) @@ -663,3 +664,55 @@ def test_geometry_field(self): "required": ["random_field1", "random_field2", "polygon"], }, ) + + def test_distance_to_point_ordering_filter(self): + path = "/" + method = "GET" + view = create_view( + GeojsonLocationOrderDistanceToPointList, "GET", create_request("/") + ) + inspector = GeoFeatureAutoSchema() + inspector.view = view + generated_schema = inspector.get_filter_parameters(path, method) + self.assertListEqual( + generated_schema, + [ + { + "name": "dist", + "required": False, + "in": "query", + "schema": {"type": "number", "format": "float", "default": 1000}, + "description": "Represents **Distance** in **Distance to point** filter. " + "Default value is used only if ***point*** is passed.", + }, + { + "name": "point", + "required": False, + "in": "query", + "description": "Point represented in **x,y** format. " + "Represents **point** in **Distance to point filter**", + "schema": { + "type": "array", + "items": {"type": "float"}, + "minItems": 2, + "maxItems": 2, + "example": [0, 10], + }, + "style": "form", + "explode": False, + }, + { + "name": "order", + "required": False, + "in": "query", + "description": "", + "schema": { + "type": "enum", + "items": {"type": "string", "enum": ["asc", "desc"]}, + "example": "desc", + }, + "style": "form", + "explode": False, + } + ], + ) diff --git a/tox.ini b/tox.ini index 39701337..35bac44f 100644 --- a/tox.ini +++ b/tox.ini @@ -27,5 +27,6 @@ deps = djangorestframework312: djangorestframework~=3.12.0 djangorestframework313: djangorestframework~=3.13.0 -rrequirements-test.txt + -rrequirements.txt pytest: pytest pytest: pytest-django