-
Notifications
You must be signed in to change notification settings - Fork 201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OpenAPI Schema Generation #223
Changes from 44 commits
d8cf934
a448b13
0859877
bce22da
75343a0
93b0f14
9f94293
c7421e4
69bc672
7b99c1e
563ce0d
9df1f4f
b0ff15c
99bf8cf
255f160
5598003
b830a74
2b7f862
e3f1494
540a7c4
2a8d1b0
f9ab386
60682e7
bbf392d
d2895d8
14ceb6b
af8773a
a94a7bf
dac80da
4a47129
f1a0a2b
d137946
2bdba3d
73bda51
e3cb217
02f34ca
1c2e457
3e7966f
e374ed4
01c1e92
59d346a
11430b4
331f764
1aefe46
8780ea6
8de2e18
27443e8
4c1dc11
e31c72d
f6ea625
fb01e25
83d3dd7
1d551ba
644c379
6abd208
1f14ee5
917b490
b1c30e6
0009d2a
5af7366
218fec5
c9ffd55
438527c
f890c91
d61949d
88d0b12
d0f8848
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,49 +38,68 @@ | |
|
||
|
||
__all__ = [ | ||
'InBBoxFilter', | ||
'InBBOXFilter', | ||
'GeometryFilter', | ||
'GeoFilterSet', | ||
'TMSTileFilter', | ||
'DistanceToPointFilter', | ||
'DistanceToPointOrderingFilter', | ||
"InBBoxFilter", | ||
"InBBOXFilter", | ||
"GeometryFilter", | ||
"GeoFilterSet", | ||
"TMSTileFilter", | ||
"DistanceToPointFilter", | ||
"DistanceToPointOrderingFilter", | ||
] | ||
|
||
|
||
class InBBoxFilter(BaseFilterBackend): | ||
bbox_param = 'in_bbox' # The URL query parameter which contains the bbox. | ||
bbox_param = "in_bbox" # The URL query parameter which contains the bbox. | ||
|
||
def get_filter_bbox(self, request): | ||
bbox_string = request.query_params.get(self.bbox_param, None) | ||
if not bbox_string: | ||
return None | ||
|
||
try: | ||
p1x, p1y, p2x, p2y = (float(n) for n in bbox_string.split(',')) | ||
p1x, p1y, p2x, p2y = (float(n) for n in bbox_string.split(",")) | ||
except ValueError: | ||
raise ParseError( | ||
'Invalid bbox string supplied for parameter {0}'.format(self.bbox_param) | ||
"Invalid bbox string supplied for parameter {0}".format(self.bbox_param) | ||
) | ||
|
||
x = Polygon.from_bbox((p1x, p1y, p2x, p2y)) | ||
return x | ||
|
||
def filter_queryset(self, request, queryset, view): | ||
filter_field = getattr(view, 'bbox_filter_field', None) | ||
include_overlapping = getattr(view, 'bbox_filter_include_overlapping', False) | ||
filter_field = getattr(view, "bbox_filter_field", None) | ||
include_overlapping = getattr(view, "bbox_filter_include_overlapping", False) | ||
if include_overlapping: | ||
geoDjango_filter = 'bboverlaps' | ||
geoDjango_filter = "bboverlaps" | ||
else: | ||
geoDjango_filter = 'contained' | ||
geoDjango_filter = "contained" | ||
|
||
if not filter_field: | ||
return queryset | ||
|
||
bbox = self.get_filter_bbox(request) | ||
if not bbox: | ||
return queryset | ||
return queryset.filter(Q(**{'%s__%s' % (filter_field, geoDjango_filter): bbox})) | ||
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 | ||
|
@@ -91,13 +110,13 @@ class GeometryFilter(django_filters.Filter): | |
field_class = forms.GeometryField | ||
|
||
def __init__(self, *args, **kwargs): | ||
kwargs.setdefault('widget', forms.TextInput) | ||
kwargs.setdefault("widget", forms.TextInput) | ||
super().__init__(*args, **kwargs) | ||
|
||
|
||
class GeoFilterSet(django_filters.FilterSet): | ||
GEOFILTER_FOR_DBFIELD_DEFAULTS = { | ||
models.GeometryField: {'filter_class': GeometryFilter}, | ||
models.GeometryField: {"filter_class": GeometryFilter}, | ||
} | ||
|
||
def __new__(cls, *args, **kwargs): | ||
|
@@ -111,38 +130,49 @@ 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) | ||
if not tile_string: | ||
return None | ||
|
||
try: | ||
z, x, y = (int(n) for n in tile_string.split('/')) | ||
z, x, y = (int(n) for n in tile_string.split("/")) | ||
except ValueError: | ||
raise ParseError( | ||
'Invalid tile string supplied for parameter {0}'.format(self.tile_param) | ||
"Invalid tile string supplied for parameter {0}".format(self.tile_param) | ||
) | ||
|
||
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' | ||
point_param = 'point' # The URL query parameter which contains the | ||
dist_param = "dist" | ||
point_param = "point" # The URL query parameter which contains the | ||
|
||
def get_filter_point(self, request, **kwargs): | ||
point_string = request.query_params.get(self.point_param, None) | ||
if not point_string: | ||
return None | ||
|
||
try: | ||
(x, y) = (float(n) for n in point_string.split(',')) | ||
(x, y) = (float(n) for n in point_string.split(",")) | ||
except ValueError: | ||
raise ParseError( | ||
'Invalid geometry string supplied for parameter {0}'.format( | ||
"Invalid geometry string supplied for parameter {0}".format( | ||
self.point_param | ||
) | ||
) | ||
|
@@ -179,9 +209,9 @@ def dist_to_deg(self, distance, latitude): | |
return distance / (earthRadius * latitudeCorrection) * rad2deg | ||
|
||
def filter_queryset(self, request, queryset, view): | ||
filter_field = getattr(view, 'distance_filter_field', None) | ||
convert_distance_input = getattr(view, 'distance_filter_convert_meters', False) | ||
geoDjango_filter = 'dwithin' # use dwithin for points | ||
filter_field = getattr(view, "distance_filter_field", None) | ||
convert_distance_input = getattr(view, "distance_filter_convert_meters", False) | ||
geoDjango_filter = "dwithin" # use dwithin for points | ||
|
||
if not filter_field: | ||
return queryset | ||
|
@@ -196,7 +226,7 @@ def filter_queryset(self, request, queryset, view): | |
dist = float(dist_string) | ||
except ValueError: | ||
raise ParseError( | ||
'Invalid distance string supplied for parameter {0}'.format( | ||
"Invalid distance string supplied for parameter {0}".format( | ||
self.dist_param | ||
) | ||
) | ||
|
@@ -206,19 +236,47 @@ def filter_queryset(self, request, queryset, view): | |
dist = self.dist_to_deg(dist, point[1]) | ||
|
||
return queryset.filter( | ||
Q(**{'%s__%s' % (filter_field, geoDjango_filter): (point, dist)}) | ||
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 | ||
order_param = 'order' | ||
order_param = "order" | ||
|
||
def filter_queryset(self, request, queryset, view): | ||
if not GeometryDistance: | ||
raise ValueError('GeometryDistance not available on this version of django') | ||
raise ValueError("GeometryDistance not available on this version of django") | ||
|
||
filter_field = getattr(view, 'distance_ordering_filter_field', None) | ||
filter_field = getattr(view, "distance_ordering_filter_field", None) | ||
|
||
if not filter_field: | ||
return queryset | ||
|
@@ -228,7 +286,25 @@ def filter_queryset(self, request, queryset, view): | |
return queryset | ||
|
||
order = request.query_params.get(self.order_param) | ||
if order == 'desc': | ||
if order == "desc": | ||
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, | ||
} | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to return the params. It is throwing an error in drf spectacular There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this implementation is not based on DRF spectacular btw There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @auvipy yes but check all implementations of get_schema_operation_parameters, they all return data. This is the only one that does not. Settings params inside of get_schema_operation_parameters and then not returning it also does not make sense since no one can access these parameters, since they are not set on self or returned. It is a get function, which should return something. In this case the params. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are you up for making a contribution? I will review it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please do not change the quoting style already in use, applies also to the other similar changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is done by black. I will find all unnecessary modifications and revert them.
At the end of all tests, while destroying the database, the
free
function throws an error. Please look into it. Not only mine but all PRs are failing because of this issue.