diff --git a/docs/changelog.rst b/docs/changelog.rst index c36ec9c..8f65af5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,25 +7,27 @@ Changelog **1.0.5** ********* -- **FIX:** fixed a crash caused by having read-only Serializers nested by reference -- **FIX:** removed erroneous backslashes in paths when routes are generated using Django 2 +- **FIXED:** fixed a crash caused by having read-only Serializers nested by reference +- **FIXED:** removed erroneous backslashes in paths when routes are generated using Django 2 `path() `_ -- **IMPROVEMENT:** updated ``swagger-ui`` to version 3.7.0 +- **IMPROVED:** updated ``swagger-ui`` to version 3.7.0 +- **IMPROVED:** ``FileField`` is now generated as an URL or file name in response Schemas + (:pr:`21`, thanks to :ghuser:`h-hirokawa`) ********* **1.0.4** ********* -- **FIX:** fixed improper generation of YAML references -- **FEATURE:** added ``query_serializer`` parameter to +- **FIXED:** fixed improper generation of YAML references +- **ADDED:** added ``query_serializer`` parameter to :func:`@swagger_auto_schema <.swagger_auto_schema>` (:issue:`16`, :pr:`17`) ********* **1.0.3** ********* -- **FIX:** fixed bug that caused schema views returned from cache to fail (:issue:`14`) -- **FIX:** disabled automatic generation of response schemas for form operations to avoid confusing errors caused by +- **FIXED:** fixed bug that caused schema views returned from cache to fail (:issue:`14`) +- **FIXED:** disabled automatic generation of response schemas for form operations to avoid confusing errors caused by attempting to shove file parameters into Schema objects ********* diff --git a/docs/conf.py b/docs/conf.py index 22c83e7..bdad022 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,6 +4,7 @@ # drf-yasg documentation build configuration file, created by # sphinx-quickstart on Sun Dec 10 15:20:34 2017. import os +import re import sys import sphinx_rtd_theme @@ -216,40 +217,59 @@ drf_yasg.views.SchemaView = drf_yasg.views.get_schema_view(None) # custom interpreted role for linking to GitHub issues and pull requests # use as :issue:`14` or :pr:`17` -gh_issue_uri = "https://github.com/axnsan12/drf-yasg/issues/%d" -gh_pr_uri = "https://github.com/axnsan12/drf-yasg/pull/%d" +gh_issue_uri = "https://github.com/axnsan12/drf-yasg/issues/{}" +gh_pr_uri = "https://github.com/axnsan12/drf-yasg/pull/{}" +gh_user_uri = "https://github.com/{}" + + +def sphinx_err(inliner, lineno, rawtext, msg): + msg = inliner.reporter.error(msg, line=lineno) + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + + +def sphinx_ref(options, rawtext, text, ref): + set_classes(options) + node = nodes.reference(rawtext, text, refuri=ref, **options) + return [node], [] + + +def role_github_user(name, rawtext, text, lineno, inliner, options=None, content=None): + options = options or {} + content = content or [] + + if not re.match(r"^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$", text): + return sphinx_err(inliner, lineno, rawtext, '"%s" is not a valid GitHub username.' % text) + + ref = gh_user_uri.format(text) + text = '@' + utils.unescape(text) + return sphinx_ref(options, rawtext, text, ref) def role_github_pull_request_or_issue(name, rawtext, text, lineno, inliner, options=None, content=None): options = options or {} content = content or [] try: - ghid = int(text) - if ghid <= 0: + if int(text) <= 0: raise ValueError except ValueError: - msg = inliner.reporter.error( - 'GitHub pull request or issue number must be a number greater than or equal to 1; ' - '"%s" is invalid.' % text, line=lineno + return sphinx_err( + inliner, lineno, rawtext, + 'GitHub pull request or issue number must be a number greater than or equal to 1; "%s" is invalid.' % text ) - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] - # Base URL mainly used by inliner.rfc_reference, so this is correct: if name == 'pr': ref = gh_pr_uri elif name == 'issue': ref = gh_issue_uri else: - msg = inliner.reporter.error('unknown tag name for GitHub reference - "%s"' % name, line=lineno) - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] + return sphinx_err(inliner, lineno, rawtext, 'unknown role name for GitHub reference - "%s"' % name) - ref = ref % ghid - set_classes(options) - node = nodes.reference(rawtext, '#' + utils.unescape(text), refuri=ref, **options) - return [node], [] + ref = ref.format(text) + text = '#' + utils.unescape(text) + return sphinx_ref(options, rawtext, text, ref) roles.register_local_role('pr', role_github_pull_request_or_issue) roles.register_local_role('issue', role_github_pull_request_or_issue) +roles.register_local_role('ghuser', role_github_user) diff --git a/src/drf_yasg/generators.py b/src/drf_yasg/generators.py index dc98408..522d27d 100644 --- a/src/drf_yasg/generators.py +++ b/src/drf_yasg/generators.py @@ -38,7 +38,6 @@ class EndpointEnumerator(_EndpointEnumerator): :return: the unescaped path :rtype: str """ - original_path = path clean_path = '' while path: match = PATH_PARAMETER_RE.search(path) diff --git a/src/drf_yasg/utils.py b/src/drf_yasg/utils.py index e8d0712..3a74ae8 100644 --- a/src/drf_yasg/utils.py +++ b/src/drf_yasg/utils.py @@ -177,12 +177,15 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, ** description = description if swagger_object_type != openapi.Items else None # Items has no description either def SwaggerType(**instance_kwargs): - if swagger_object_type == openapi.Parameter: + if swagger_object_type == openapi.Parameter and 'required' not in instance_kwargs: instance_kwargs['required'] = field.required - if swagger_object_type != openapi.Items: + if swagger_object_type != openapi.Items and 'default' not in instance_kwargs: default = getattr(field, 'default', serializers.empty) if default is not serializers.empty: instance_kwargs['default'] = default + if swagger_object_type == openapi.Schema and 'read_only' not in instance_kwargs: + if field.read_only: + instance_kwargs['read_only'] = True instance_kwargs.update(kwargs) return swagger_object_type(title=title, description=description, **instance_kwargs) @@ -214,8 +217,6 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, ** required = [] for key, value in serializer.fields.items(): properties[key] = serializer_field_to_swagger(value, ChildSwaggerType, definitions) - if value.read_only and not isinstance(properties[key], openapi._Ref): - properties[key].read_only = value.read_only if value.required: required.append(key) @@ -287,19 +288,20 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, ** elif isinstance(field, serializers.FileField): # swagger 2.0 does not support specifics about file fields, so ImageFile gets no special treatment # OpenAPI 3.0 does support it, so a future implementation could handle this better - err = SwaggerGenerationError("parameter of type file is supported only in a formData Parameter") + err = SwaggerGenerationError("FileField is supported only in a formData Parameter or response Schema") if swagger_object_type == openapi.Schema: # FileField.to_representation returns URL or file name result = SwaggerType(type=openapi.TYPE_STRING, read_only=True) if getattr(field, 'use_url', api_settings.UPLOADED_FILES_USE_URL): result.format = openapi.FORMAT_URI return result - elif swagger_object_type != openapi.Parameter: + elif swagger_object_type == openapi.Parameter: + param = SwaggerType(type=openapi.TYPE_FILE) + if param['in'] != openapi.IN_FORM: + raise err # pragma: no cover + return param + else: raise err # pragma: no cover - param = SwaggerType(type=openapi.TYPE_FILE) - if param['in'] != openapi.IN_FORM: - raise err # pragma: no cover - return param elif isinstance(field, serializers.DictField) and swagger_object_type == openapi.Schema: child_schema = serializer_field_to_swagger(field.child, ChildSwaggerType, definitions) return SwaggerType( diff --git a/testproj/articles/serializers.py b/testproj/articles/serializers.py index 63f0abb..a01ad8c 100644 --- a/testproj/articles/serializers.py +++ b/testproj/articles/serializers.py @@ -15,7 +15,7 @@ class ArticleSerializer(serializers.ModelSerializer): model = Article fields = ('title', 'body', 'slug', 'date_created', 'date_modified', 'references', 'uuid', 'cover', 'cover_name') - read_only_fields = ('date_created', 'date_modified', 'cover') + read_only_fields = ('date_created', 'date_modified') lookup_field = 'slug' extra_kwargs = {'body': {'help_text': 'body serializer help_text'}}