Skip to content

Commit

Permalink
Merge 4c1dc11 into 624973e
Browse files Browse the repository at this point in the history
  • Loading branch information
dhaval-mehta authored Oct 25, 2020
2 parents 624973e + 4c1dc11 commit ffb2e4b
Show file tree
Hide file tree
Showing 12 changed files with 1,152 additions and 7 deletions.
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@ branches:
- master

before_install:
- sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable -y
- sudo apt-get update -q
- sudo apt-get install binutils libproj-dev gdal-bin -y
- sudo apt-get install binutils libproj-dev -y
- pip install -U pip setuptools wheel
- pip install -U -r requirements-test.txt
install:
Expand Down
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ Federico Capoano https://github.com/nemesisdesign/
Shanto https://github.com/Shanto
Eric Theise https://github.com/erictheise
Asif Saifuddin https://github.com/auvipy
Dhaval Mehta https://github.com/dhaval-mehta
22 changes: 22 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,28 @@ will order the results by the distance to the point (-122.4862, 37.7694).
We can also reverse the order of the results by passing ``order=desc``:
``/location/?point=-122.4862,37.7694&order=desc&format=json``

Schema Generation
=================

Note: Schema generation support is available only for DRF >= 3.12

Simplest Approach would be, change DEFAULT_SCHEMA_CLASS to `rest_framework_gis.schema.GeoFeatureAutoSchema`

.. code-block:: python
REST_FRAMEWORK = {
...
'DEFAULT_SCHEMA_CLASS': 'rest_framework_gis.schema.GeoFeatureAutoSchema',
...
}
If you do not want to change default schema generator class:

- You can pass this class as an argument to `get_schema_view` `[Ref] <https://www.django-rest-framework.org/api-guide/schemas/#generating-a-dynamic-schema-with-schemaview>`__
- You can pass this class as an argument to `generateschema` command `[Ref] <https://www.django-rest-framework.org/api-guide/schemas/#generating-a-static-schema-with-the-generateschema-management-command>`__



Running the tests
-----------------

Expand Down
78 changes: 77 additions & 1 deletion rest_framework_gis/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,25 @@ def filter_queryset(self, request, queryset, view):
return queryset
return queryset.filter(Q(**{'%s__%s' % (filter_field, geoDjango_filter): bbox}))

def get_schema_operation_parameters(self, view):
return [
{
"name": self.bbox_param,
"required": False,
"in": "query",
"description": "Specify a bounding box as filter: in_bbox=min_lon,min_lat,max_lon,max_lat",
"schema": {
"type": "array",
"items": {"type": "float"},
"minItems": 4,
"maxItems": 4,
"example": [0, 0, 10, 10],
},
"style": "form",
"explode": False,
},
]


# backward compatibility
InBBOXFilter = InBBoxFilter
Expand Down Expand Up @@ -111,7 +130,7 @@ def __new__(cls, *args, **kwargs):


class TMSTileFilter(InBBoxFilter):
tile_param = 'tile' # The URL query paramater which contains the tile address
tile_param = 'tile' # The URL query parameter which contains the tile address

def get_filter_bbox(self, request):
tile_string = request.query_params.get(self.tile_param, None)
Expand All @@ -128,6 +147,17 @@ def get_filter_bbox(self, request):
bbox = Polygon.from_bbox(tile_edges(x, y, z))
return bbox

def get_schema_operation_parameters(self, view):
return [
{
"name": self.tile_param,
"required": False,
"in": "query",
"description": "Specify a bounding box filter defined by a TMS tile address: tile=Z/X/Y",
"schema": {"type": "string", "example": "12/56/34"},
},
]


class DistanceToPointFilter(BaseFilterBackend):
dist_param = 'dist'
Expand Down Expand Up @@ -209,6 +239,34 @@ def filter_queryset(self, request, queryset, view):
Q(**{'%s__%s' % (filter_field, geoDjango_filter): (point, dist)})
)

def get_schema_operation_parameters(self, view):
return [
{
"name": self.dist_param,
"required": False,
"in": "query",
"schema": {"type": "number", "format": "float", "default": 1000},
"description": f"Represents **Distance** in **Distance to point** filter. "
f"Default value is used only if ***{self.point_param}*** is passed.",
},
{
"name": self.point_param,
"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,
},
]


class DistanceToPointOrderingFilter(DistanceToPointFilter):
srid = 4326
Expand All @@ -232,3 +290,21 @@ def filter_queryset(self, request, queryset, view):
return queryset.order_by(-GeometryDistance(filter_field, point))
else:
return queryset.order_by(GeometryDistance(filter_field, point))

def get_schema_operation_parameters(self, view):
params = super().get_schema_operation_parameters(view)
params.append(
{
"name": self.order_param,
"required": False,
"in": "query",
"description": "",
"schema": {
"type": "enum",
"items": {"type": "string", "enum": ["asc", "desc"]},
"example": "desc",
},
"style": "form",
"explode": False,
}
)
9 changes: 9 additions & 0 deletions rest_framework_gis/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,12 @@ def get_paginated_response(self, data):
]
)
)

def get_paginated_response_schema(self, view):
schema = super().get_paginated_response_schema(view)
schema["properties"]["features"] = schema["properties"].pop("results")
schema["properties"] = {
"type": {"type": "string", "enum": ["FeatureCollection"]},
**schema["properties"],
}
return schema
154 changes: 154 additions & 0 deletions rest_framework_gis/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import warnings

from django.contrib.gis.db import models
from rest_framework.schemas.openapi import AutoSchema
from rest_framework.utils import model_meta

from rest_framework_gis.fields import GeometrySerializerMethodField
from rest_framework_gis.serializers import (
GeoFeatureModelListSerializer,
GeoFeatureModelSerializer,
)


class GeoFeatureAutoSchema(AutoSchema):
COORDINATES_SCHEMA_FOR_POINT = {
"type": "array",
"items": {"type": "number", "format": "float"},
"example": [12.9721, 77.5933],
"minItems": 2,
"maxItems": 3,
}

COORDINATES_SCHEMA_FOR_LINE_STRING = {
"type": "array",
"items": COORDINATES_SCHEMA_FOR_POINT,
"example": [[22.4707, 70.0577], [12.9721, 77.5933]],
"minItems": 2,
}

GEO_FIELD_TO_SCHEMA = {
models.PointField: {
"type": {"type": "string", "enum": ["Point"]},
"coordinates": COORDINATES_SCHEMA_FOR_POINT,
},
models.LineStringField: {
"type": {"type": "string", "enum": ["LineString"]},
"coordinates": COORDINATES_SCHEMA_FOR_LINE_STRING,
},
models.PolygonField: {
"type": {"type": "string", "enum": ["Polygon"]},
"coordinates": {
"type": "array",
"items": {**COORDINATES_SCHEMA_FOR_LINE_STRING, "minItems": 4},
"example": [
[0.0, 0.0],
[0.0, 50.0],
[50.0, 50.0],
[50.0, 0.0],
[0.0, 0.0],
],
},
},
}

MULTI_FIELD_MAPPING = {
models.PointField: models.MultiPointField,
models.LineStringField: models.MultiLineStringField,
models.PolygonField: models.MultiPolygonField,
}

for singular_field, multi_field in MULTI_FIELD_MAPPING.items():
GEO_FIELD_TO_SCHEMA[multi_field] = {
"type": {"type": "string", "enum": [multi_field.geom_class.__name__]},
"coordinates": {
"type": "array",
"items": GEO_FIELD_TO_SCHEMA[singular_field]["coordinates"],
"example": [
GEO_FIELD_TO_SCHEMA[singular_field]["coordinates"]["example"]
],
},
}

def _map_geo_field(self, serializer, geo_field_name):
field = serializer.fields[geo_field_name]
if isinstance(field, GeometrySerializerMethodField):
warnings.warn(
"Geometry generation for GeometrySerializerMethodField is not supported."
)
return {}

model_field_name = geo_field_name

geo_field = model_meta.get_field_info(serializer.Meta.model).fields[
model_field_name
]
try:
return self.GEO_FIELD_TO_SCHEMA[geo_field.__class__]
except KeyError:
warnings.warn(
"Geometry generation for {field} is not supported.".format(field=field)
)
return {}

def map_field(self, field):
if isinstance(field, GeoFeatureModelListSerializer):
return self._map_geo_feature_model_list_serializer(field)

return super().map_field(field)

def _map_geo_feature_model_list_serializer(self, serializer):
return {
"type": "object",
"properties": {
"type": {"type": "string", "enum": ["FeatureCollection"]},
"features": {
"type": "array",
"items": self.map_serializer(serializer.child),
},
},
}

def _map_geo_feature_model_serializer(self, serializer):
schema = super().map_serializer(serializer)

geo_json_schema = {
"type": "object",
"properties": {"type": {"type": "string", "enum": ["Feature"]}},
}

if serializer.Meta.id_field:
geo_json_schema["properties"]["id"] = schema["properties"].pop(
serializer.Meta.id_field
)

geo_field = serializer.Meta.geo_field
geo_json_schema["properties"]["geometry"] = {
"type": "object",
"properties": self._map_geo_field(serializer, geo_field),
}
schema["properties"].pop(geo_field)

if serializer.Meta.auto_bbox or serializer.Meta.bbox_geo_field:
geo_json_schema["properties"]["bbox"] = {
"type": "array",
"items": {"type": "number"},
"minItems": 4,
"maxItems": 4,
"example": [12.9721, 77.5933, 12.9721, 77.5933],
}
if serializer.Meta.bbox_geo_field in schema["properties"]:
schema["properties"].pop(serializer.Meta.bbox_geo_field)

geo_json_schema["properties"]["properties"] = schema

return geo_json_schema

def map_serializer(self, serializer):
if isinstance(serializer, GeoFeatureModelListSerializer):
return self._map_geo_feature_model_list_serializer(serializer)

if isinstance(serializer, GeoFeatureModelSerializer):
return self._map_geo_feature_model_serializer(serializer)

return super().map_serializer(serializer)
Loading

0 comments on commit ffb2e4b

Please sign in to comment.