diff --git a/src/drf_yasg/utils.py b/src/drf_yasg/utils.py index 2a71c3e..9621fc2 100644 --- a/src/drf_yasg/utils.py +++ b/src/drf_yasg/utils.py @@ -6,7 +6,7 @@ from collections import OrderedDict from django.db import models from django.utils.encoding import force_text from rest_framework import serializers, status -from rest_framework.mixins import DestroyModelMixin, RetrieveModelMixin, UpdateModelMixin +from rest_framework.mixins import DestroyModelMixin, RetrieveModelMixin, UpdateModelMixin, ListModelMixin from rest_framework.parsers import FileUploadParser from rest_framework.request import is_form_media_type from rest_framework.settings import api_settings as rest_framework_settings @@ -226,6 +226,10 @@ def is_list_view(path, method, view): # a detail action is surely not a list route return False + # for GenericAPIView, if it's a list view then it should be a list view + if isinstance(view, ListModelMixin): + return True + # for GenericAPIView, if it's a detail view it can't also be a list view if isinstance(view, (RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin)): return False diff --git a/testproj/snippets/migrations/0003_snippetviewer.py b/testproj/snippets/migrations/0003_snippetviewer.py new file mode 100644 index 0000000..ca8932e --- /dev/null +++ b/testproj/snippets/migrations/0003_snippetviewer.py @@ -0,0 +1,24 @@ +# Generated by Django 2.1.7 on 2019-03-16 14:06 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('snippets', '0002_auto_20181219_1016'), + ] + + operations = [ + migrations.CreateModel( + name='SnippetViewer', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('snippet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='viewers', to='snippets.Snippet')), + ('viewer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='snippet_views', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/testproj/snippets/models.py b/testproj/snippets/models.py index d565095..1ad36c5 100644 --- a/testproj/snippets/models.py +++ b/testproj/snippets/models.py @@ -18,3 +18,8 @@ class Snippet(models.Model): class Meta: ordering = ('created',) + + +class SnippetViewer(models.Model): + snippet = models.ForeignKey(Snippet, on_delete=models.CASCADE, related_name='viewers') + viewer = models.ForeignKey('auth.User', related_name='snippet_views', on_delete=models.CASCADE) diff --git a/testproj/snippets/serializers.py b/testproj/snippets/serializers.py index 03d18c8..949203b 100644 --- a/testproj/snippets/serializers.py +++ b/testproj/snippets/serializers.py @@ -4,7 +4,7 @@ from django.contrib.auth import get_user_model from rest_framework import serializers from rest_framework.compat import MaxLengthValidator, MinValueValidator -from snippets.models import LANGUAGE_CHOICES, STYLE_CHOICES, Snippet +from snippets.models import LANGUAGE_CHOICES, STYLE_CHOICES, Snippet, SnippetViewer class LanguageSerializer(serializers.Serializer): @@ -100,3 +100,9 @@ class SnippetSerializer(serializers.Serializer): instance.style = validated_data.get('style', instance.style) instance.save() return instance + + +class SnippetViewerSerializer(serializers.ModelSerializer): + class Meta: + model = SnippetViewer + fields = '__all__' diff --git a/testproj/snippets/urls.py b/testproj/snippets/urls.py index dd34aa5..6dc23e8 100644 --- a/testproj/snippets/urls.py +++ b/testproj/snippets/urls.py @@ -8,10 +8,13 @@ if django.VERSION[:2] >= (2, 0): urlpatterns = [ path('', views.SnippetList.as_view()), path('/', views.SnippetDetail.as_view()), + path('views//', views.SnippetViewerList.as_view()), ] else: from django.conf.urls import url + urlpatterns = [ url('^$', views.SnippetList.as_view()), url(r'^(?P\d+)/$', views.SnippetDetail.as_view()), + url(r'^views/(?P\d+)/$', views.SnippetViewerList.as_view()), ] diff --git a/testproj/snippets/views.py b/testproj/snippets/views.py index bf90d83..95193b7 100644 --- a/testproj/snippets/views.py +++ b/testproj/snippets/views.py @@ -2,13 +2,15 @@ from djangorestframework_camel_case.parser import CamelCaseJSONParser from djangorestframework_camel_case.render import CamelCaseJSONRenderer from inflection import camelize from rest_framework import generics, status +from rest_framework.generics import get_object_or_404 +from rest_framework.pagination import PageNumberPagination from rest_framework.parsers import FormParser, FileUploadParser from drf_yasg import openapi from drf_yasg.inspectors import SwaggerAutoSchema from drf_yasg.utils import swagger_auto_schema -from snippets.models import Snippet -from snippets.serializers import SnippetSerializer +from snippets.models import Snippet, SnippetViewer +from snippets.serializers import SnippetSerializer, SnippetViewerSerializer class CamelCaseOperationIDAutoSchema(SwaggerAutoSchema): @@ -93,3 +95,31 @@ class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): def delete(self, request, *args, **kwargs): """delete method docstring""" return super(SnippetDetail, self).patch(request, *args, **kwargs) + + +class SnippetViewerList(generics.ListAPIView): + """SnippetViewerList classdoc""" + serializer_class = SnippetViewerSerializer + pagination_class = PageNumberPagination + + parser_classes = (FormParser, CamelCaseJSONParser, FileUploadParser) + renderer_classes = (CamelCaseJSONRenderer,) + swagger_schema = CamelCaseOperationIDAutoSchema + lookup_url_kwarg = 'snippet_pk' + + def get_object(self): + queryset = Snippet.objects.all() + + # Perform the lookup filtering. + lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field + + filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} + obj = get_object_or_404(queryset, **filter_kwargs) + + # May raise a permission denied + self.check_object_permissions(self.request, obj) + + return obj + + def get_queryset(self): + return SnippetViewer.objects.filter(snippet=self.get_object()) diff --git a/tests/reference.yaml b/tests/reference.yaml index fd6832b..00d8919 100644 --- a/tests/reference.yaml +++ b/tests/reference.yaml @@ -440,6 +440,46 @@ paths: tags: - snippets parameters: [] + /snippets/views/{snippet_pk}/: + get: + operationId: snippetsViewsRead + description: SnippetViewerList classdoc + parameters: + - name: page + in: query + description: A page number within the paginated result set. + required: false + type: integer + responses: + '200': + description: '' + schema: + required: + - count + - results + type: object + properties: + count: + type: integer + next: + type: string + format: uri + x-nullable: true + previous: + type: string + format: uri + x-nullable: true + results: + type: array + items: + $ref: '#/definitions/SnippetViewer' + tags: + - snippets + parameters: + - name: snippet_pk + in: path + required: true + type: string /snippets/{id}/: get: operationId: snippetsRead @@ -1590,6 +1630,22 @@ definitions: format: decimal default: 0.0 minimum: 0.0 + SnippetViewer: + required: + - snippet + - viewer + type: object + properties: + id: + title: ID + type: integer + readOnly: true + snippet: + title: Snippet + type: integer + viewer: + title: Viewer + type: integer Todo: required: - title