Skip to content

Commit

Permalink
Fix lookup_value_regex handling of non string types
Browse files Browse the repository at this point in the history
  • Loading branch information
lotvall committed Oct 22, 2024
1 parent 0dea78c commit da20fdb
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 8 deletions.
6 changes: 5 additions & 1 deletion drf_spectacular/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ def _resolve_path_parameters(self, variables):
resolved_parameter = resolve_django_path_parameter(
self.path_regex, variable, self.map_renderers('format'),
)
if not resolved_parameter:
if not resolved_parameter and model is None:
resolved_parameter = resolve_regex_path_parameter(self.path_regex, variable)

if resolved_parameter:
Expand All @@ -519,6 +519,10 @@ def _resolve_path_parameters(self, variables):
model_field_name = variable
model_field = follow_model_field_lookup(model, model_field_name)
schema = self._map_model_field(model_field, direction=None)
if 'type' in schema and schema['type'] == 'string':
regex_resolved_parameter = resolve_regex_path_parameter(self.path_regex, variable)
if regex_resolved_parameter:
schema = regex_resolved_parameter['schema']
if 'description' not in schema and model_field.primary_key:
description = get_pk_description(model, model_field)
except django_exceptions.FieldError:
Expand Down
38 changes: 38 additions & 0 deletions tests/contrib/test_drf_lookup_value.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import pytest
from django.db import models
from django.urls import include, re_path
from rest_framework import serializers, viewsets
from rest_framework.routers import SimpleRouter

from tests import assert_schema, generate_schema


class Book(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=255)

class BookSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Book
fields = ('name',)

def _generate_simple_router_schema(viewset):
router = SimpleRouter()
router.register('books', viewset, basename='books')
urlpatterns = [
re_path('', include(router.urls)),
]
return generate_schema(None, patterns=urlpatterns)

@pytest.mark.contrib('rest_framework_lookup_value')
def test_drf_lookup_value_regex_integer(no_warnings):
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
lookup_field = 'id'
lookup_value_regex = r'\d+'

assert_schema(
_generate_simple_router_schema(BookViewSet),
'tests/contrib/test_drf_lookup_value.yml',
)
194 changes: 194 additions & 0 deletions tests/contrib/test_drf_lookup_value.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
openapi: 3.0.3
info:
title: ''
version: 0.0.0
paths:
/books/:
get:
operationId: books_list
tags:
- books
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Book'
description: ''
post:
operationId: books_create
tags:
- books
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/Book'
multipart/form-data:
schema:
$ref: '#/components/schemas/Book'
required: true
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
description: ''
/books/{id}/:
get:
operationId: books_retrieve
parameters:
- in: path
name: id
schema:
type: integer
maximum: 9223372036854775807
minimum: -9223372036854775808
format: int64
description: A unique value identifying this book.
required: true
tags:
- books
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
description: ''
put:
operationId: books_update
parameters:
- in: path
name: id
schema:
type: integer
maximum: 9223372036854775807
minimum: -9223372036854775808
format: int64
description: A unique value identifying this book.
required: true
tags:
- books
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/Book'
multipart/form-data:
schema:
$ref: '#/components/schemas/Book'
required: true
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
description: ''
patch:
operationId: books_partial_update
parameters:
- in: path
name: id
schema:
type: integer
maximum: 9223372036854775807
minimum: -9223372036854775808
format: int64
description: A unique value identifying this book.
required: true
tags:
- books
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedBook'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/PatchedBook'
multipart/form-data:
schema:
$ref: '#/components/schemas/PatchedBook'
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
description: ''
delete:
operationId: books_destroy
parameters:
- in: path
name: id
schema:
type: integer
maximum: 9223372036854775807
minimum: -9223372036854775808
format: int64
description: A unique value identifying this book.
required: true
tags:
- books
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'204':
description: No response body
components:
schemas:
Book:
type: object
properties:
name:
type: string
maxLength: 255
required:
- name
PatchedBook:
type: object
properties:
name:
type: string
maxLength: 255
securitySchemes:
basicAuth:
type: http
scheme: basic
cookieAuth:
type: apiKey
in: cookie
name: sessionid
4 changes: 0 additions & 4 deletions tests/contrib/test_drf_nested_routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,4 @@ def get_queryset(self):
assert_schema(
_generate_nested_routers_schema(RootViewSet, ChildViewSet),
'tests/contrib/test_drf_nested_routers.yml',
reverse_transforms=[
lambda x: x.replace('format: uuid', 'pattern: ^[0-9]+$'),
lambda x: x.replace('\n description: A UUID string identifying this root.', '')
]
)
6 changes: 3 additions & 3 deletions tests/test_regressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2420,12 +2420,12 @@ class PathParameterLookupModel(models.Model):
# untyped -> get from model
(path, '/{id}/', '<pk>/', ['integer']),
# non-default pattern -> use
(re_path, '/{id}/', r'(?P<pk>[a-z]{2}(-[a-z]{2})?)/', ['string']),
(re_path, '/{id}/', r'(?P<pk>[a-z]{2}(-[a-z]{2})?)/', ['integer']),
# default pattern -> get from model
(re_path, '/{id}/', r'(?P<pk>[^/.]+)/$', ['integer']),
# same mechanics for non-pk field discovery from model
(re_path, '/{field}/t/{id}/', r'^(?P<field>[^/.]+)/t/(?P<pk>[a-z]+)/', ['integer', 'string']),
(re_path, '/{field}/t/{id}/', r'^(?P<field>[A-Z\(\)]+)/t/(?P<pk>[^/.]+)/', ['string', 'integer']),
(re_path, '/{field}/t/{id}/', r'^(?P<field>[^/.]+)/t/(?P<pk>[a-z]+)/', ['integer', 'integer']),
(re_path, '/{field}/t/{id}/', r'^(?P<field>[A-Z\(\)]+)/t/(?P<pk>[^/.]+)/', ['integer', 'integer']),
])
def test_path_parameter_priority_matching(no_warnings, path_func, path_str, pattern, parameter_types):
class LookupSerializer(serializers.ModelSerializer):
Expand Down

0 comments on commit da20fdb

Please sign in to comment.