Skip to content
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

Fix inheritance bugs with @extend_schema_view(). #554

Merged
merged 1 commit into from
Oct 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions drf_spectacular/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,12 +473,14 @@ def extend_schema_view(**kwargs) -> Callable[[F], F]:
calls as values
"""
def wrapping_decorator(method_decorator, method):
@method_decorator
@functools.wraps(method)
def wrapped_method(self, request, *args, **kwargs):
return method(self, request, *args, **kwargs)

return wrapped_method
if hasattr(method, 'kwargs'):
wrapped_method.kwargs = method.kwargs.copy()

return method_decorator(wrapped_method)

def decorator(view):
view_methods = {m.__name__: m for m in get_view_methods(view)}
Expand Down
19 changes: 17 additions & 2 deletions tests/test_extend_schema_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class Meta:
extended_action=extend_schema(description='view extended action description'),
raw_action=extend_schema(description='view raw action description'),
)
class XViewset(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
class XViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
queryset = ESVModel.objects.all()
serializer_class = ESVSerializer

Expand All @@ -52,9 +52,24 @@ class YViewSet(viewsets.ModelViewSet):
queryset = ESVModel.objects.all()


# view to make sure that schema applied to a subclass does not affect its parent.
@extend_schema_view(
list=extend_schema(exclude=True),
retrieve=extend_schema(description='overridden description for child only'),
extended_action=extend_schema(responses={200: {'type': 'string', 'pattern': r'^[0-9]{4}(?:-[0-9]{2}){2}$'}}),
raw_action=extend_schema(summary="view raw action summary"),
)
class ZViewSet(XViewSet):
@extend_schema(tags=['child-tag'])
@action(detail=False, methods=['GET'])
def raw_action(self, request):
return Response('2019-03-01')
Comment on lines +56 to +66
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tests the child with @extend_schema_view inheriting from a parent with @extend_schema_view scenario.

In this case:

  • We want list to be removed from the schema only for the child.
  • We want retrieve to have description overridden only for the child, but retain tags from the parent.
  • We want extended_action to have responses overridden only for the child, but retain description from the parent.
  • We want raw_action to override the tags via @extend_schema in the child and summary via @extend_schema_view on the child without affecting the parent.
    • The only thing I'm unsure about here is that we don't get the description from the parent's @extend_schema_view, but we would get the tags from the @extend_schema on the parent if we weren't overriding it. Perhaps this needs to be tweaked to ensure that we still get description?



router = routers.SimpleRouter()
router.register('x', XViewset)
router.register('x', XViewSet)
router.register('y', YViewSet)
router.register('z', ZViewSet)
urlpatterns = router.urls


Expand Down
59 changes: 59 additions & 0 deletions tests/test_extend_schema_view.yml
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,65 @@ paths:
responses:
'204':
description: No response body
/z/{id}/:
get:
operationId: z_retrieve
description: overridden description for child only
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this esv model.
required: true
tags:
- custom-retrieve-tag
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ESV'
description: ''
/z/extended_action/:
get:
operationId: z_extended_action_retrieve
description: view extended action description
tags:
- global-tag
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
content:
application/json:
schema:
type: string
pattern: ^[0-9]{4}(?:-[0-9]{2}){2}$
description: ''
/z/raw_action/:
get:
operationId: z_raw_action_retrieve
summary: view raw action summary
tags:
- child-tag
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ESV'
description: ''
components:
schemas:
ESV:
Expand Down
35 changes: 35 additions & 0 deletions tests/test_regressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2569,3 +2569,38 @@ def custom_action(self):

schema = generate_schema('x', viewset=XViewSet)
schema['paths']['/x/{id}/custom_action/']['get']['summary'] == 'A custom action!'


def test_extend_schema_view_isolation(no_warnings):

class Animal(models.Model):
pass

class AnimalSerializer(serializers.ModelSerializer):
class Meta:
model = Animal
fields = '__all__'

class AnimalViewSet(viewsets.GenericViewSet):
serializer_class = AnimalSerializer
queryset = Animal.objects.all()

@action(detail=False)
def notes(self, request):
pass # pragma: no cover

@extend_schema_view(notes=extend_schema(summary='List mammals.'))
class MammalViewSet(AnimalViewSet):
pass

@extend_schema_view(notes=extend_schema(summary='List insects.'))
class InsectViewSet(AnimalViewSet):
pass

router = routers.SimpleRouter()
router.register('api/mammals', MammalViewSet)
router.register('api/insects', InsectViewSet)

schema = generate_schema(None, patterns=router.urls)
assert schema['paths']['/api/mammals/notes/']['get']['summary'] == 'List mammals.'
assert schema['paths']['/api/insects/notes/']['get']['summary'] == 'List insects.'