Skip to content

Commit da20fdb

Browse files
committed
Fix lookup_value_regex handling of non string types
1 parent 0dea78c commit da20fdb

File tree

5 files changed

+240
-8
lines changed

5 files changed

+240
-8
lines changed

drf_spectacular/openapi.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ def _resolve_path_parameters(self, variables):
496496
resolved_parameter = resolve_django_path_parameter(
497497
self.path_regex, variable, self.map_renderers('format'),
498498
)
499-
if not resolved_parameter:
499+
if not resolved_parameter and model is None:
500500
resolved_parameter = resolve_regex_path_parameter(self.path_regex, variable)
501501

502502
if resolved_parameter:
@@ -519,6 +519,10 @@ def _resolve_path_parameters(self, variables):
519519
model_field_name = variable
520520
model_field = follow_model_field_lookup(model, model_field_name)
521521
schema = self._map_model_field(model_field, direction=None)
522+
if 'type' in schema and schema['type'] == 'string':
523+
regex_resolved_parameter = resolve_regex_path_parameter(self.path_regex, variable)
524+
if regex_resolved_parameter:
525+
schema = regex_resolved_parameter['schema']
522526
if 'description' not in schema and model_field.primary_key:
523527
description = get_pk_description(model, model_field)
524528
except django_exceptions.FieldError:
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import pytest
2+
from django.db import models
3+
from django.urls import include, re_path
4+
from rest_framework import serializers, viewsets
5+
from rest_framework.routers import SimpleRouter
6+
7+
from tests import assert_schema, generate_schema
8+
9+
10+
class Book(models.Model):
11+
id = models.IntegerField(primary_key=True)
12+
name = models.CharField(max_length=255)
13+
14+
class BookSerializer(serializers.HyperlinkedModelSerializer):
15+
class Meta:
16+
model = Book
17+
fields = ('name',)
18+
19+
def _generate_simple_router_schema(viewset):
20+
router = SimpleRouter()
21+
router.register('books', viewset, basename='books')
22+
urlpatterns = [
23+
re_path('', include(router.urls)),
24+
]
25+
return generate_schema(None, patterns=urlpatterns)
26+
27+
@pytest.mark.contrib('rest_framework_lookup_value')
28+
def test_drf_lookup_value_regex_integer(no_warnings):
29+
class BookViewSet(viewsets.ModelViewSet):
30+
queryset = Book.objects.all()
31+
serializer_class = BookSerializer
32+
lookup_field = 'id'
33+
lookup_value_regex = r'\d+'
34+
35+
assert_schema(
36+
_generate_simple_router_schema(BookViewSet),
37+
'tests/contrib/test_drf_lookup_value.yml',
38+
)
+194
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
openapi: 3.0.3
2+
info:
3+
title: ''
4+
version: 0.0.0
5+
paths:
6+
/books/:
7+
get:
8+
operationId: books_list
9+
tags:
10+
- books
11+
security:
12+
- cookieAuth: []
13+
- basicAuth: []
14+
- {}
15+
responses:
16+
'200':
17+
content:
18+
application/json:
19+
schema:
20+
type: array
21+
items:
22+
$ref: '#/components/schemas/Book'
23+
description: ''
24+
post:
25+
operationId: books_create
26+
tags:
27+
- books
28+
requestBody:
29+
content:
30+
application/json:
31+
schema:
32+
$ref: '#/components/schemas/Book'
33+
application/x-www-form-urlencoded:
34+
schema:
35+
$ref: '#/components/schemas/Book'
36+
multipart/form-data:
37+
schema:
38+
$ref: '#/components/schemas/Book'
39+
required: true
40+
security:
41+
- cookieAuth: []
42+
- basicAuth: []
43+
- {}
44+
responses:
45+
'201':
46+
content:
47+
application/json:
48+
schema:
49+
$ref: '#/components/schemas/Book'
50+
description: ''
51+
/books/{id}/:
52+
get:
53+
operationId: books_retrieve
54+
parameters:
55+
- in: path
56+
name: id
57+
schema:
58+
type: integer
59+
maximum: 9223372036854775807
60+
minimum: -9223372036854775808
61+
format: int64
62+
description: A unique value identifying this book.
63+
required: true
64+
tags:
65+
- books
66+
security:
67+
- cookieAuth: []
68+
- basicAuth: []
69+
- {}
70+
responses:
71+
'200':
72+
content:
73+
application/json:
74+
schema:
75+
$ref: '#/components/schemas/Book'
76+
description: ''
77+
put:
78+
operationId: books_update
79+
parameters:
80+
- in: path
81+
name: id
82+
schema:
83+
type: integer
84+
maximum: 9223372036854775807
85+
minimum: -9223372036854775808
86+
format: int64
87+
description: A unique value identifying this book.
88+
required: true
89+
tags:
90+
- books
91+
requestBody:
92+
content:
93+
application/json:
94+
schema:
95+
$ref: '#/components/schemas/Book'
96+
application/x-www-form-urlencoded:
97+
schema:
98+
$ref: '#/components/schemas/Book'
99+
multipart/form-data:
100+
schema:
101+
$ref: '#/components/schemas/Book'
102+
required: true
103+
security:
104+
- cookieAuth: []
105+
- basicAuth: []
106+
- {}
107+
responses:
108+
'200':
109+
content:
110+
application/json:
111+
schema:
112+
$ref: '#/components/schemas/Book'
113+
description: ''
114+
patch:
115+
operationId: books_partial_update
116+
parameters:
117+
- in: path
118+
name: id
119+
schema:
120+
type: integer
121+
maximum: 9223372036854775807
122+
minimum: -9223372036854775808
123+
format: int64
124+
description: A unique value identifying this book.
125+
required: true
126+
tags:
127+
- books
128+
requestBody:
129+
content:
130+
application/json:
131+
schema:
132+
$ref: '#/components/schemas/PatchedBook'
133+
application/x-www-form-urlencoded:
134+
schema:
135+
$ref: '#/components/schemas/PatchedBook'
136+
multipart/form-data:
137+
schema:
138+
$ref: '#/components/schemas/PatchedBook'
139+
security:
140+
- cookieAuth: []
141+
- basicAuth: []
142+
- {}
143+
responses:
144+
'200':
145+
content:
146+
application/json:
147+
schema:
148+
$ref: '#/components/schemas/Book'
149+
description: ''
150+
delete:
151+
operationId: books_destroy
152+
parameters:
153+
- in: path
154+
name: id
155+
schema:
156+
type: integer
157+
maximum: 9223372036854775807
158+
minimum: -9223372036854775808
159+
format: int64
160+
description: A unique value identifying this book.
161+
required: true
162+
tags:
163+
- books
164+
security:
165+
- cookieAuth: []
166+
- basicAuth: []
167+
- {}
168+
responses:
169+
'204':
170+
description: No response body
171+
components:
172+
schemas:
173+
Book:
174+
type: object
175+
properties:
176+
name:
177+
type: string
178+
maxLength: 255
179+
required:
180+
- name
181+
PatchedBook:
182+
type: object
183+
properties:
184+
name:
185+
type: string
186+
maxLength: 255
187+
securitySchemes:
188+
basicAuth:
189+
type: http
190+
scheme: basic
191+
cookieAuth:
192+
type: apiKey
193+
in: cookie
194+
name: sessionid

tests/contrib/test_drf_nested_routers.py

-4
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,4 @@ def get_queryset(self):
8888
assert_schema(
8989
_generate_nested_routers_schema(RootViewSet, ChildViewSet),
9090
'tests/contrib/test_drf_nested_routers.yml',
91-
reverse_transforms=[
92-
lambda x: x.replace('format: uuid', 'pattern: ^[0-9]+$'),
93-
lambda x: x.replace('\n description: A UUID string identifying this root.', '')
94-
]
9591
)

tests/test_regressions.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -2420,12 +2420,12 @@ class PathParameterLookupModel(models.Model):
24202420
# untyped -> get from model
24212421
(path, '/{id}/', '<pk>/', ['integer']),
24222422
# non-default pattern -> use
2423-
(re_path, '/{id}/', r'(?P<pk>[a-z]{2}(-[a-z]{2})?)/', ['string']),
2423+
(re_path, '/{id}/', r'(?P<pk>[a-z]{2}(-[a-z]{2})?)/', ['integer']),
24242424
# default pattern -> get from model
24252425
(re_path, '/{id}/', r'(?P<pk>[^/.]+)/$', ['integer']),
24262426
# same mechanics for non-pk field discovery from model
2427-
(re_path, '/{field}/t/{id}/', r'^(?P<field>[^/.]+)/t/(?P<pk>[a-z]+)/', ['integer', 'string']),
2428-
(re_path, '/{field}/t/{id}/', r'^(?P<field>[A-Z\(\)]+)/t/(?P<pk>[^/.]+)/', ['string', 'integer']),
2427+
(re_path, '/{field}/t/{id}/', r'^(?P<field>[^/.]+)/t/(?P<pk>[a-z]+)/', ['integer', 'integer']),
2428+
(re_path, '/{field}/t/{id}/', r'^(?P<field>[A-Z\(\)]+)/t/(?P<pk>[^/.]+)/', ['integer', 'integer']),
24292429
])
24302430
def test_path_parameter_priority_matching(no_warnings, path_func, path_str, pattern, parameter_types):
24312431
class LookupSerializer(serializers.ModelSerializer):

0 commit comments

Comments
 (0)