Prepare for 1.1.0 (#30)

* refactor the view inspection process to be more modular and allow recursive customization
* add operation_id argument to @swagger_auto_
* add inspections for min/max validators
* add support for URLPathVersioning and NamespaceVersioning
* integrate with djangorestframework-camel-case
* fix bugs, improve tests and documentation
This commit is contained in:
Cristi Vîjdea
2017-12-26 22:50:59 +01:00
committed by GitHub
parent 73adc49b2c
commit c89f96fcb0
52 changed files with 2167 additions and 854 deletions
+4 -3
View File
@@ -1,11 +1,12 @@
from rest_framework import serializers
from articles.models import Article
from django.utils.translation import ugettext_lazy as _
class ArticleSerializer(serializers.ModelSerializer):
references = serializers.DictField(
help_text="this is a really bad example",
help_text=_("this is a really bad example"),
child=serializers.URLField(help_text="but i needed to test these 2 fields somehow"),
read_only=True,
)
@@ -23,8 +24,8 @@ class ArticleSerializer(serializers.ModelSerializer):
'body': {'help_text': 'body serializer help_text'},
'author': {
'default': serializers.CurrentUserDefault(),
'help_text': "The ID of the user that created this article; if none is provided, "
"defaults to the currently logged in user."
'help_text': _("The ID of the user that created this article; if none is provided, "
"defaults to the currently logged in user.")
},
}
+41 -4
View File
@@ -11,11 +11,45 @@ from rest_framework.response import Response
from articles import serializers
from articles.models import Article
from drf_yasg.inspectors import SwaggerAutoSchema
from drf_yasg import openapi
from drf_yasg.app_settings import swagger_settings
from drf_yasg.inspectors import SwaggerAutoSchema, FieldInspector, CoreAPICompatInspector, NotHandled
from drf_yasg.utils import swagger_auto_schema
class NoPagingAutoSchema(SwaggerAutoSchema):
class DjangoFilterDescriptionInspector(CoreAPICompatInspector):
def get_filter_parameters(self, filter_backend):
if isinstance(filter_backend, DjangoFilterBackend):
result = super(DjangoFilterDescriptionInspector, self).get_filter_parameters(filter_backend)
for param in result:
if not param.get('description', ''):
param.description = "Filter the returned list by {field_name}".format(field_name=param.name)
return result
return NotHandled
class NoSchemaTitleInspector(FieldInspector):
def process_result(self, result, method_name, obj, **kwargs):
# remove the `title` attribute of all Schema objects
if isinstance(result, openapi.Schema.OR_REF):
# traverse any references and alter the Schema object in place
schema = openapi.resolve_ref(result, self.components)
schema.pop('title', None)
# no ``return schema`` here, because it would mean we always generate
# an inline `object` instead of a definition reference
# return back the same object that we got - i.e. a reference if we got a reference
return result
class NoTitleAutoSchema(SwaggerAutoSchema):
field_inspectors = [NoSchemaTitleInspector] + swagger_settings.DEFAULT_FIELD_INSPECTORS
class NoPagingAutoSchema(NoTitleAutoSchema):
def should_page(self):
return False
@@ -26,7 +60,8 @@ class ArticlePagination(LimitOffsetPagination):
@method_decorator(name='list', decorator=swagger_auto_schema(
operation_description="description from swagger_auto_schema via method_decorator"
operation_description="description from swagger_auto_schema via method_decorator",
filter_inspectors=[DjangoFilterDescriptionInspector]
))
class ArticleViewSet(viewsets.ModelViewSet):
"""
@@ -52,7 +87,9 @@ class ArticleViewSet(viewsets.ModelViewSet):
ordering_fields = ('date_modified', 'date_created')
ordering = ('date_created',)
@swagger_auto_schema(auto_schema=NoPagingAutoSchema)
swagger_schema = NoTitleAutoSchema
@swagger_auto_schema(auto_schema=NoPagingAutoSchema, filter_inspectors=[DjangoFilterDescriptionInspector])
@list_route(methods=['get'])
def today(self, request):
today_min = datetime.datetime.combine(datetime.date.today(), datetime.time.min)
+4
View File
@@ -0,0 +1,4 @@
from django.contrib.auth.models import User
User.objects.filter(username='admin').delete()
User.objects.create_superuser('admin', 'admin@admin.admin', 'passwordadmin')
Binary file not shown.
+6 -2
View File
@@ -5,18 +5,22 @@ from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class LanguageSerializer(serializers.Serializer):
__ref_name__ = None
name = serializers.ChoiceField(
choices=LANGUAGE_CHOICES, default='python', help_text='The name of the programming language')
class Meta:
ref_name = None
class ExampleProjectSerializer(serializers.Serializer):
__ref_name__ = 'Project'
project_name = serializers.CharField(help_text='Name of the project')
github_repo = serializers.CharField(required=True, help_text='Github repository of the project')
class Meta:
ref_name = 'Project'
class SnippetSerializer(serializers.Serializer):
"""SnippetSerializer classdoc
+18
View File
@@ -1,14 +1,28 @@
from djangorestframework_camel_case.parser import CamelCaseJSONParser
from djangorestframework_camel_case.render import CamelCaseJSONRenderer
from inflection import camelize
from rest_framework import generics
from drf_yasg.inspectors import SwaggerAutoSchema
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
class CamelCaseOperationIDAutoSchema(SwaggerAutoSchema):
def get_operation_id(self, operation_keys):
operation_id = super(CamelCaseOperationIDAutoSchema, self).get_operation_id(operation_keys)
return camelize(operation_id, uppercase_first_letter=False)
class SnippetList(generics.ListCreateAPIView):
"""SnippetList classdoc"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
parser_classes = (CamelCaseJSONParser,)
renderer_classes = (CamelCaseJSONRenderer,)
swagger_schema = CamelCaseOperationIDAutoSchema
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
@@ -31,6 +45,10 @@ class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
serializer_class = SnippetSerializer
pagination_class = None
parser_classes = (CamelCaseJSONParser,)
renderer_classes = (CamelCaseJSONRenderer,)
swagger_schema = CamelCaseOperationIDAutoSchema
def patch(self, request, *args, **kwargs):
"""patch method docstring"""
return super(SnippetDetail, self).patch(request, *args, **kwargs)
+49
View File
@@ -128,3 +128,52 @@ USE_TZ = True
STATIC_URL = '/static/'
TEST_RUNNER = 'testproj.runner.PytestTestRunner'
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
'formatters': {
'pipe_separated': {
'format': '%(asctime)s | %(levelname)s | %(name)s | %(message)s'
}
},
'handlers': {
'console_log': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'stream': 'ext://sys.stdout',
'formatter': 'pipe_separated',
},
},
'loggers': {
'drf_yasg': {
'handlers': ['console_log'],
'level': 'DEBUG',
'propagate': False,
},
'django': {
'handlers': ['console_log'],
'level': 'DEBUG',
'propagate': False,
},
'django.db.backends': {
'handlers': ['console_log'],
'level': 'INFO',
'propagate': False,
},
'django.template': {
'handlers': ['console_log'],
'level': 'INFO',
'propagate': False,
},
'swagger_spec_validator': {
'handlers': ['console_log'],
'level': 'INFO',
'propagate': False,
}
},
'root': {
'handlers': ['console_log'],
'level': 'INFO',
}
}
+7 -7
View File
@@ -6,7 +6,7 @@ from rest_framework.decorators import api_view
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
schema_view = get_schema_view(
SchemaView = get_schema_view(
openapi.Info(
title="Snippets API",
default_version='v1',
@@ -27,12 +27,12 @@ def plain_view(request):
urlpatterns = [
url(r'^swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
url(r'^cached/swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'),
url(r'^cached/swagger/$', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'),
url(r'^cached/redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
url(r'^swagger(?P<format>.json|.yaml)$', SchemaView.without_ui(cache_timeout=0), name='schema-json'),
url(r'^swagger/$', SchemaView.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
url(r'^redoc/$', SchemaView.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
url(r'^cached/swagger(?P<format>.json|.yaml)$', SchemaView.without_ui(cache_timeout=None), name='cschema-json'),
url(r'^cached/swagger/$', SchemaView.with_ui('swagger', cache_timeout=None), name='cschema-swagger-ui'),
url(r'^cached/redoc/$', SchemaView.with_ui('redoc', cache_timeout=None), name='cschema-redoc'),
url(r'^admin/', admin.site.urls),
url(r'^snippets/', include('snippets.urls')),
+1 -1
View File
@@ -6,7 +6,7 @@ from snippets.models import Snippet
class UserSerializerrr(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
article_slugs = serializers.SlugRelatedField(read_only=True, slug_field='slug', many=True, source='articlessss')
article_slugs = serializers.SlugRelatedField(read_only=True, slug_field='slug', many=True, source='articles')
last_connected_ip = serializers.IPAddressField(help_text="i'm out of ideas", protocol='ipv4', read_only=True)
last_connected_at = serializers.DateField(help_text="really?", read_only=True)
+1 -1
View File
@@ -32,7 +32,7 @@ class UserList(APIView):
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
@swagger_auto_schema(request_body=no_body, operation_description="dummy operation")
@swagger_auto_schema(request_body=no_body, operation_id="users_dummy", operation_description="dummy operation")
def patch(self, request):
pass