Skip to content
This repository was archived by the owner on Jan 11, 2021. It is now read-only.

Commit 310ba63

Browse files
committed
Merge branch 'develop'
2 parents 9457a15 + 094c845 commit 310ba63

File tree

11 files changed

+592
-127
lines changed

11 files changed

+592
-127
lines changed

CHANGELOG.md

Lines changed: 477 additions & 114 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,5 +89,5 @@ Many thanks to Tom Christie & all the contributors who have developed [Django RE
8989
[pypi]: https://pypi.python.org/pypi/django-rest-swagger
9090
[license-badge]: https://img.shields.io/pypi/l/django-rest-swagger.svg
9191
[license]: https://pypi.python.org/pypi/django-rest-swagger/
92-
[docs-badge]: https://readthedocs.org/projects/django-rest-swagger/badge/
93-
[docs]: http://django-rest-swagger.readthedocs.org/
92+
[docs-badge]: https://readthedocs.io/projects/django-rest-swagger/badge/
93+
[docs]: http://django-rest-swagger.readthedocs.io/

docs/source/misc.rst

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Markdown
66
django-rest-swagger will parse docstrings as markdown if `Markdown <https://pypi.python.org/pypi/Markdown>`_ is installed.
77

88
reStructuredText
9-
-----------------
9+
----------------
1010
django-rest-swagger can be configured to parse docstrings as reStructuredText.
1111

1212
Add to your settings:
@@ -18,7 +18,7 @@ Add to your settings:
1818
}
1919
2020
Swagger 'nickname' attribute
21-
-----------------
21+
----------------------------
2222
By default, django-rest-swagger uses django-rest-framework's get_view_name to resolve the `nickname` attribute
2323
of a Swagger operation. You can specify an alternative function for `nickname` resolution using the following setting:
2424

@@ -36,4 +36,18 @@ This function should use the following signature:
3636
3737
-:code:`cls` The view class providing the operation.
3838

39-
-:code:`suffix` The string name of the class method which is providing the operation.
39+
-:code:`suffix` The string name of the class method which is providing the operation.
40+
41+
42+
Swagger 'list' views
43+
--------------------
44+
45+
django-rest-swagger introspects your views and viewset methods in order to determine the serializer used.
46+
47+
In the majority of cases, the object returned is a single type. However, there are times where multiple serialized
48+
objects can be returned, such as in the case of `list` methods.
49+
50+
When you use ViewSets, django-rest-swagger will report that the `list` method on a viewset returns a list of objects.
51+
52+
For other ViewSet methods or function based views, you can also hint to django-rest-swagger that the view response is
53+
also a list, rather than a single object. See :ref:`many`

docs/source/settings.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Example:
1212
'exclude_namespaces': [],
1313
'api_version': '0.1',
1414
'api_path': '/',
15+
'relative_paths': False,
1516
'enabled_methods': [
1617
'get',
1718
'post',
@@ -154,6 +155,13 @@ Then in app/views.py:
154155
from django.http import HttpResponse
155156
return HttpResponse('you have no permissions!')
156157
158+
relative_paths
159+
--------------
160+
161+
set to True to make API paths relative to specified :code:`api_path`.
162+
163+
Default: :code:`False`
164+
157165
resource_access_handler
158166
-------------------------
159167

docs/source/yaml.rst

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Example:
3030
3131
serializer: .serializers.FooSerializer
3232
omit_serializer: false
33+
many: true
3334
3435
parameters_strategy: merge
3536
omit_parameters:
@@ -97,7 +98,7 @@ to populate :code:`type` you can specify it with :code:`pytype`:
9798
pytype: .serializers.FooSerializer
9899
99100
Overriding parameters
100-
--------------------
101+
---------------------
101102

102103
parameters_strategy
103104
~~~~~~~~~~~~~~~~~~~
@@ -176,6 +177,23 @@ signature as follows:
176177
required: false
177178
type: url
178179
180+
.. _many:
181+
182+
many
183+
----
184+
185+
In cases where an API response is a list of objects, it is possible to mark
186+
this to django-rest-swagger by overriding :code:`many` to `True`.
187+
188+
.. code-block:: yaml
189+
190+
many: true
191+
192+
This overrides the :code:`type` returned to be an array of the resolved API
193+
type. ViewSet :code:`list` methods do not require this definition, and are
194+
marked as :code:`many` automatically.
195+
196+
179197
responseMessages
180198
---------------------------------
181199
To document error codes that your APIView might throw

rest_framework_swagger/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
VERSION = '0.3.5'
1+
VERSION = '0.3.7'
22

33
DEFAULT_SWAGGER_SETTINGS = {
44
'exclude_url_names': [],
55
'exclude_namespaces': [],
66
'api_version': '',
77
'api_path': '/',
88
'api_key': '',
9+
'relative_paths': False,
910
'token_type': 'Token',
1011
'enabled_methods': ['get', 'post', 'put', 'patch', 'delete'],
1112
'is_authenticated': False,

rest_framework_swagger/docgenerator.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ def get_operations(self, api, apis=None):
119119
if produces:
120120
operation['produces'] = produces
121121

122+
# Check if this method has been reported as returning an
123+
# array response
124+
if method_introspector.is_array_response:
125+
operation['items'] = {
126+
'$ref': operation['type']
127+
}
128+
operation['type'] = 'array'
129+
122130
operations.append(operation)
123131

124132
return operations

rest_framework_swagger/introspectors.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,11 @@ def __init__(self, view_introspector, method):
181181
self.path = view_introspector.path
182182
self.user = view_introspector.user
183183

184+
@property
185+
def is_array_response(self):
186+
""" Support definition of array responses with the 'many' attr """
187+
return self.get_yaml_parser().object.get('many')
188+
184189
def get_module(self):
185190
return self.callback.__module__
186191

@@ -669,6 +674,12 @@ def __init__(self, view_introspector, method, http_method):
669674
.__init__(view_introspector, method)
670675
self.http_method = http_method.upper()
671676

677+
@property
678+
def is_array_response(self):
679+
""" ViewSet.list methods always return array responses """
680+
return (self.method == 'list' or
681+
super(ViewSetMethodIntrospector, self).is_array_response)
682+
672683
def get_http_method(self):
673684
return self.http_method
674685

rest_framework_swagger/static/rest_framework_swagger/lib/swagger.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ var SwaggerOperation = function(nickname, path, method, parameters, summary, not
694694

695695
// for 1.1 compatibility
696696
var type = param.type || param.dataType;
697-
if(type === 'array') {
697+
if(type === 'array' && 'items' in param) {
698698
type = 'array[' + (param.items.$ref ? param.items.$ref : param.items.type) + ']';
699699
}
700700
param.type = type;

rest_framework_swagger/urlparser.py

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,34 @@
44

55
from django.conf import settings
66
from django.utils import six
7+
from django.utils.six.moves.urllib_parse import urljoin
78
from django.core.urlresolvers import RegexURLResolver, RegexURLPattern
89
from django.contrib.admindocs.views import simplify_regex
910

1011
from rest_framework.views import APIView
1112

1213
from .apidocview import APIDocView
14+
from . import SWAGGER_SETTINGS
1315

1416

1517
class UrlParser(object):
1618

17-
def get_apis(self, patterns=None, urlconf=None, filter_path=None, exclude_url_names=[], exclude_namespaces=[]):
19+
__relative_path_matcher__ = re.compile(
20+
r'^%s(?P<relative>.*)' % SWAGGER_SETTINGS.get('api_path', ''))
21+
22+
def get_apis(self, patterns=None, urlconf=None, filter_path=None,
23+
exclude_url_names=None, exclude_namespaces=None):
1824
"""
1925
Returns all the DRF APIViews found in the project URLs
2026
2127
patterns -- supply list of patterns (optional)
2228
exclude_url_names -- list of url names to ignore (optional)
2329
exclude_namespaces -- list of namespaces to ignore (optional)
2430
"""
31+
exclude_url_names = exclude_url_names or []
32+
exclude_namespaces = exclude_namespaces or []
33+
filter_path = self.__make_absolute__(filter_path)
34+
2535
if patterns is None and urlconf is not None:
2636
if isinstance(urlconf, six.string_types):
2737
urls = import_module(urlconf)
@@ -39,6 +49,7 @@ def get_apis(self, patterns=None, urlconf=None, filter_path=None, exclude_url_na
3949
exclude_namespaces=exclude_namespaces,
4050
)
4151
if filter_path is not None:
52+
filter_path = self.__make_relative__(filter_path, strip=True)
4253
return self.get_filtered_apis(apis, filter_path)
4354

4455
return apis
@@ -115,6 +126,7 @@ def __assemble_endpoint_data__(self, pattern, prefix='', filter_path=None):
115126
return None
116127

117128
path = path.replace('<', '{').replace('>', '}')
129+
path = self.__make_relative__(path)
118130

119131
if self.__exclude_format_endpoints__(path):
120132
return
@@ -125,18 +137,22 @@ def __assemble_endpoint_data__(self, pattern, prefix='', filter_path=None):
125137
'callback': callback,
126138
}
127139

128-
def __flatten_patterns_tree__(self, patterns, prefix='', filter_path=None, exclude_url_names=[], exclude_namespaces=[]):
140+
def __flatten_patterns_tree__(self, patterns, prefix='', filter_path=None,
141+
exclude_url_names=None, exclude_namespaces=None):
129142
"""
130143
Uses recursion to flatten url tree.
131144
132145
patterns -- urlpatterns list
133146
prefix -- (optional) Prefix for URL pattern
134147
"""
148+
exclude_url_names = exclude_url_names or []
149+
exclude_namespaces = exclude_namespaces or []
135150
pattern_list = []
136151

137152
for pattern in patterns:
138153
if isinstance(pattern, RegexURLPattern):
139-
endpoint_data = self.__assemble_endpoint_data__(pattern, prefix, filter_path=filter_path)
154+
endpoint_data = self.__assemble_endpoint_data__(
155+
pattern, prefix, filter_path=filter_path)
140156

141157
if endpoint_data is None or pattern.name in exclude_url_names:
142158
continue
@@ -145,7 +161,8 @@ def __flatten_patterns_tree__(self, patterns, prefix='', filter_path=None, exclu
145161

146162
elif isinstance(pattern, RegexURLResolver):
147163

148-
if pattern.namespace is not None and pattern.namespace in exclude_namespaces:
164+
if pattern.namespace is not None \
165+
and pattern.namespace in exclude_namespaces:
149166
continue
150167

151168
pref = prefix + pattern.regex.pattern
@@ -196,3 +213,24 @@ def __exclude_format_endpoints__(self, path):
196213
return True
197214

198215
return False
216+
217+
def __make_relative__(self, path, strip=False):
218+
"""
219+
When `relative_paths` is True, make path relative to API Path.
220+
"""
221+
if path:
222+
if SWAGGER_SETTINGS.get('relative_paths', False):
223+
res = UrlParser.__relative_path_matcher__.match(path)
224+
path = urljoin('/', res.groups()[0]) if res else path
225+
226+
return path.strip('/') if strip else path
227+
228+
def __make_absolute__(self, path):
229+
"""
230+
When `relative_paths` is True, fully qualify path with API Path.
231+
"""
232+
if path:
233+
if SWAGGER_SETTINGS.get('relative_paths', False):
234+
path = urljoin(SWAGGER_SETTINGS.get('api_path'), path)
235+
236+
return path

0 commit comments

Comments
 (0)