Skip to content

Commit

Permalink
feat: custom django path's param convert type tfranzel#502
Browse files Browse the repository at this point in the history
  • Loading branch information
jtamm-red committed Sep 7, 2021
1 parent 1407059 commit e6a6bf6
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 8 deletions.
38 changes: 30 additions & 8 deletions drf_spectacular/plumbing.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
ReverseOneToOneDescriptor,
)
from django.db.models.fields.reverse_related import ForeignObjectRel
from django.urls.converters import get_converter as django_get_converter
from django.urls.resolvers import ( # type: ignore
_PATH_PARAMETER_COMPONENT_RE, RegexPattern, Resolver404, RoutePattern, URLPattern, URLResolver,
get_resolver,
Expand All @@ -33,7 +34,7 @@
from rest_framework.utils.mediatypes import _MediaType
from uritemplate import URITemplate

from drf_spectacular.drainage import error, warn
from drf_spectacular.drainage import error, warn, has_override, get_override
from drf_spectacular.settings import spectacular_settings
from drf_spectacular.types import (
DJANGO_PATH_CONVERTER_MAPPING, OPENAPI_TYPE_MAPPING, PYTHON_TYPE_MAPPING, OpenApiTypes,
Expand Down Expand Up @@ -680,13 +681,34 @@ def resolve_regex_path_parameter(path_regex, variable, available_formats):
if api_settings.SCHEMA_COERCE_PATH_PK and parameter == 'pk':
parameter = 'id'

if parameter == variable and converter in DJANGO_PATH_CONVERTER_MAPPING:
return build_parameter_type(
name=parameter,
schema=build_basic_type(DJANGO_PATH_CONVERTER_MAPPING[converter]),
location=OpenApiParameter.PATH,
enum=enum_values,
)
if converter and parameter == variable:
if converter in DJANGO_PATH_CONVERTER_MAPPING:
return build_parameter_type(
name=parameter,
schema=build_basic_type(DJANGO_PATH_CONVERTER_MAPPING[converter]),
location=OpenApiParameter.PATH,
enum=enum_values,
)
else:
django_converter = django_get_converter(converter)
if django_converter:
converter_schema = None
converter_regex = getattr(django_converter, 'regex')
if has_override(django_converter, 'field'):
annotation = get_override(django_converter, 'field')
converter_schema = build_basic_type(annotation) if is_basic_type(annotation) else annotation
elif converter_regex:
converter_schema = {
'type': 'string',
'pattern': converter_regex
}
if converter_schema:
return build_parameter_type(
name=parameter,
schema=converter_schema,
location=OpenApiParameter.PATH,
enum=enum_values,
)

return None

Expand Down
45 changes: 45 additions & 0 deletions tests/test_regressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,51 @@ def pi(request, foo):
assert parameter['schema']['type'] == 'integer'


def test_regex_path_parameter_discovery_with_custom_converter(no_warnings):
@extend_schema(responses=OpenApiTypes.FLOAT)
@api_view(['GET'])
def pi(request, foo):
pass # pragma: no cover

@extend_schema_field(OpenApiTypes.INT)
class CustomNumberConverter:
regex = '\\d+'

def to_python(self, value):
return int(value)

def to_url(self, value):
return '%d' % value

from django.urls import register_converter
register_converter(CustomNumberConverter, 'number')

urlpatterns = [re_path(r'^/pi/<number:precision>', pi)]
schema = generate_schema(None, patterns=urlpatterns)
parameter = schema['paths']['/pi/{precision}']['get']['parameters'][0]
assert parameter['name'] == 'precision'
assert parameter['in'] == 'path'
assert parameter['schema']['type'] == 'integer'

class CustomDateConverter:
regex = '\\d{4}-\\d{2}-\\d{2}'

def to_python(self, value):
return int(value)

def to_url(self, value):
return '%d' % value

register_converter(CustomDateConverter, 'date')
urlpatterns = [re_path(r'^/pi/<date:from_date>', pi)]
schema = generate_schema(None, patterns=urlpatterns)
parameter = schema['paths']['/pi/{from_date}']['get']['parameters'][0]
assert parameter['name'] == 'from_date'
assert parameter['in'] == 'path'
assert parameter['schema']['type'] == 'string'
assert parameter['schema']['pattern'] == '\\d{4}-\\d{2}-\\d{2}'


def test_lib_serializer_naming_collision_resolution(no_warnings):
""" parity test in tests.test_warnings.test_serializer_name_reuse """
def x_lib1():
Expand Down

0 comments on commit e6a6bf6

Please sign in to comment.