mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-07 13:54:47 +03:00
Added SCHEMA_COERCE_PATH_PK and SCHEMA_COERCE_METHOD_NAMES
This commit is contained in:
parent
fcf932f118
commit
18aebbbe01
|
@ -234,6 +234,28 @@ Default:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Schema generation controls
|
||||||
|
|
||||||
|
#### SCHEMA_COERCE_PATH_PK
|
||||||
|
|
||||||
|
If set, this maps the `'pk'` identifier in the URL conf onto the actual field
|
||||||
|
name when generating a schema path parameter. Typically this will be `'id'`.
|
||||||
|
This gives a more suitable representation as "primary key" is an implementation
|
||||||
|
detail, wheras "identifier" is a more general concept.
|
||||||
|
|
||||||
|
Default: `True`
|
||||||
|
|
||||||
|
#### SCHEMA_COERCE_METHOD_NAMES
|
||||||
|
|
||||||
|
If set, this is used to map internal viewset method names onto external action
|
||||||
|
names used in the schema generation. This allows us to generate names that
|
||||||
|
are more suitable for an external representation than those that are used
|
||||||
|
internally in the codebase.
|
||||||
|
|
||||||
|
Default: `{'retrieve': 'read', 'destroy': 'delete'}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Content type controls
|
## Content type controls
|
||||||
|
|
||||||
#### URL_FORMAT_OVERRIDE
|
#### URL_FORMAT_OVERRIDE
|
||||||
|
|
|
@ -88,7 +88,7 @@ The first thing we need to get started on our Web API is to provide a way of ser
|
||||||
|
|
||||||
|
|
||||||
class SnippetSerializer(serializers.Serializer):
|
class SnippetSerializer(serializers.Serializer):
|
||||||
pk = serializers.IntegerField(read_only=True)
|
id = serializers.IntegerField(read_only=True)
|
||||||
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
|
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
|
||||||
code = serializers.CharField(style={'base_template': 'textarea.html'})
|
code = serializers.CharField(style={'base_template': 'textarea.html'})
|
||||||
linenos = serializers.BooleanField(required=False)
|
linenos = serializers.BooleanField(required=False)
|
||||||
|
@ -144,13 +144,13 @@ We've now got a few snippet instances to play with. Let's take a look at serial
|
||||||
|
|
||||||
serializer = SnippetSerializer(snippet)
|
serializer = SnippetSerializer(snippet)
|
||||||
serializer.data
|
serializer.data
|
||||||
# {'pk': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}
|
# {'id': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}
|
||||||
|
|
||||||
At this point we've translated the model instance into Python native datatypes. To finalize the serialization process we render the data into `json`.
|
At this point we've translated the model instance into Python native datatypes. To finalize the serialization process we render the data into `json`.
|
||||||
|
|
||||||
content = JSONRenderer().render(serializer.data)
|
content = JSONRenderer().render(serializer.data)
|
||||||
content
|
content
|
||||||
# '{"pk": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'
|
# '{"id": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'
|
||||||
|
|
||||||
Deserialization is similar. First we parse a stream into Python native datatypes...
|
Deserialization is similar. First we parse a stream into Python native datatypes...
|
||||||
|
|
||||||
|
@ -175,7 +175,7 @@ We can also serialize querysets instead of model instances. To do so we simply
|
||||||
|
|
||||||
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
|
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
|
||||||
serializer.data
|
serializer.data
|
||||||
# [OrderedDict([('pk', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('pk', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('pk', 3), ('title', u''), ('code', u'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
|
# [OrderedDict([('id', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', u''), ('code', u'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
|
||||||
|
|
||||||
## Using ModelSerializers
|
## Using ModelSerializers
|
||||||
|
|
||||||
|
@ -259,12 +259,12 @@ Note that because we want to be able to POST to this view from clients that won'
|
||||||
We'll also need a view which corresponds to an individual snippet, and can be used to retrieve, update or delete the snippet.
|
We'll also need a view which corresponds to an individual snippet, and can be used to retrieve, update or delete the snippet.
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def snippet_detail(request, pk):
|
def snippet_detail(request, id):
|
||||||
"""
|
"""
|
||||||
Retrieve, update or delete a code snippet.
|
Retrieve, update or delete a code snippet.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
snippet = Snippet.objects.get(pk=pk)
|
snippet = Snippet.objects.get(id=id)
|
||||||
except Snippet.DoesNotExist:
|
except Snippet.DoesNotExist:
|
||||||
return HttpResponse(status=404)
|
return HttpResponse(status=404)
|
||||||
|
|
||||||
|
@ -291,7 +291,7 @@ Finally we need to wire these views up. Create the `snippets/urls.py` file:
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^snippets/$', views.snippet_list),
|
url(r'^snippets/$', views.snippet_list),
|
||||||
url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
|
url(r'^snippets/(?P<id>[0-9]+)/$', views.snippet_detail),
|
||||||
]
|
]
|
||||||
|
|
||||||
We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our snippet app's URLs.
|
We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our snippet app's URLs.
|
||||||
|
|
|
@ -66,12 +66,12 @@ Our instance view is an improvement over the previous example. It's a little mo
|
||||||
Here is the view for an individual snippet, in the `views.py` module.
|
Here is the view for an individual snippet, in the `views.py` module.
|
||||||
|
|
||||||
@api_view(['GET', 'PUT', 'DELETE'])
|
@api_view(['GET', 'PUT', 'DELETE'])
|
||||||
def snippet_detail(request, pk):
|
def snippet_detail(request, id):
|
||||||
"""
|
"""
|
||||||
Retrieve, update or delete a snippet instance.
|
Retrieve, update or delete a snippet instance.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
snippet = Snippet.objects.get(pk=pk)
|
snippet = Snippet.objects.get(id=id)
|
||||||
except Snippet.DoesNotExist:
|
except Snippet.DoesNotExist:
|
||||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ Start by adding a `format` keyword argument to both of the views, like so.
|
||||||
|
|
||||||
and
|
and
|
||||||
|
|
||||||
def snippet_detail(request, pk, format=None):
|
def snippet_detail(request, id, format=None):
|
||||||
|
|
||||||
Now update the `urls.py` file slightly, to append a set of `format_suffix_patterns` in addition to the existing URLs.
|
Now update the `urls.py` file slightly, to append a set of `format_suffix_patterns` in addition to the existing URLs.
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ Now update the `urls.py` file slightly, to append a set of `format_suffix_patter
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^snippets/$', views.snippet_list),
|
url(r'^snippets/$', views.snippet_list),
|
||||||
url(r'^snippets/(?P<pk>[0-9]+)$', views.snippet_detail),
|
url(r'^snippets/(?P<id>[0-9]+)$', views.snippet_detail),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = format_suffix_patterns(urlpatterns)
|
urlpatterns = format_suffix_patterns(urlpatterns)
|
||||||
|
|
|
@ -36,27 +36,27 @@ So far, so good. It looks pretty similar to the previous case, but we've got be
|
||||||
"""
|
"""
|
||||||
Retrieve, update or delete a snippet instance.
|
Retrieve, update or delete a snippet instance.
|
||||||
"""
|
"""
|
||||||
def get_object(self, pk):
|
def get_object(self, id):
|
||||||
try:
|
try:
|
||||||
return Snippet.objects.get(pk=pk)
|
return Snippet.objects.get(id=id)
|
||||||
except Snippet.DoesNotExist:
|
except Snippet.DoesNotExist:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
def get(self, request, pk, format=None):
|
def get(self, request, id, format=None):
|
||||||
snippet = self.get_object(pk)
|
snippet = self.get_object(id)
|
||||||
serializer = SnippetSerializer(snippet)
|
serializer = SnippetSerializer(snippet)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
def put(self, request, pk, format=None):
|
def put(self, request, id, format=None):
|
||||||
snippet = self.get_object(pk)
|
snippet = self.get_object(id)
|
||||||
serializer = SnippetSerializer(snippet, data=request.data)
|
serializer = SnippetSerializer(snippet, data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
def delete(self, request, pk, format=None):
|
def delete(self, request, id, format=None):
|
||||||
snippet = self.get_object(pk)
|
snippet = self.get_object(id)
|
||||||
snippet.delete()
|
snippet.delete()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ We'll also need to refactor our `urls.py` slightly now we're using class-based v
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^snippets/$', views.SnippetList.as_view()),
|
url(r'^snippets/$', views.SnippetList.as_view()),
|
||||||
url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
|
url(r'^snippets/(?P<id>[0-9]+)/$', views.SnippetDetail.as_view()),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = format_suffix_patterns(urlpatterns)
|
urlpatterns = format_suffix_patterns(urlpatterns)
|
||||||
|
|
|
@ -88,7 +88,7 @@ Make sure to also import the `UserSerializer` class
|
||||||
Finally we need to add those views into the API, by referencing them from the URL conf. Add the following to the patterns in `urls.py`.
|
Finally we need to add those views into the API, by referencing them from the URL conf. Add the following to the patterns in `urls.py`.
|
||||||
|
|
||||||
url(r'^users/$', views.UserList.as_view()),
|
url(r'^users/$', views.UserList.as_view()),
|
||||||
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
|
url(r'^users/(?P<id>[0-9]+)/$', views.UserDetail.as_view()),
|
||||||
|
|
||||||
## Associating Snippets with Users
|
## Associating Snippets with Users
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ The `r'^api-auth/'` part of pattern can actually be whatever URL you want to use
|
||||||
|
|
||||||
Now if you open up the browser again and refresh the page you'll see a 'Login' link in the top right of the page. If you log in as one of the users you created earlier, you'll be able to create code snippets again.
|
Now if you open up the browser again and refresh the page you'll see a 'Login' link in the top right of the page. If you log in as one of the users you created earlier, you'll be able to create code snippets again.
|
||||||
|
|
||||||
Once you've created a few code snippets, navigate to the '/users/' endpoint, and notice that the representation includes a list of the snippet pks that are associated with each user, in each user's 'snippets' field.
|
Once you've created a few code snippets, navigate to the '/users/' endpoint, and notice that the representation includes a list of the snippet ids that are associated with each user, in each user's 'snippets' field.
|
||||||
|
|
||||||
## Object level permissions
|
## Object level permissions
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ We'll add a url pattern for our new API root in `snippets/urls.py`:
|
||||||
|
|
||||||
And then add a url pattern for the snippet highlights:
|
And then add a url pattern for the snippet highlights:
|
||||||
|
|
||||||
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),
|
url(r'^snippets/(?P<id>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),
|
||||||
|
|
||||||
## Hyperlinking our API
|
## Hyperlinking our API
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ In this case we'd like to use a hyperlinked style between entities. In order to
|
||||||
|
|
||||||
The `HyperlinkedModelSerializer` has the following differences from `ModelSerializer`:
|
The `HyperlinkedModelSerializer` has the following differences from `ModelSerializer`:
|
||||||
|
|
||||||
* It does not include the `pk` field by default.
|
* It does not include the `id` field by default.
|
||||||
* It includes a `url` field, using `HyperlinkedIdentityField`.
|
* It includes a `url` field, using `HyperlinkedIdentityField`.
|
||||||
* Relationships use `HyperlinkedRelatedField`,
|
* Relationships use `HyperlinkedRelatedField`,
|
||||||
instead of `PrimaryKeyRelatedField`.
|
instead of `PrimaryKeyRelatedField`.
|
||||||
|
@ -80,7 +80,7 @@ We can easily re-write our existing serializers to use hyperlinking. In your `sn
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Snippet
|
model = Snippet
|
||||||
fields = ('url', 'pk', 'highlight', 'owner',
|
fields = ('url', 'id', 'highlight', 'owner',
|
||||||
'title', 'code', 'linenos', 'language', 'style')
|
'title', 'code', 'linenos', 'language', 'style')
|
||||||
|
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ We can easily re-write our existing serializers to use hyperlinking. In your `sn
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ('url', 'pk', 'username', 'snippets')
|
fields = ('url', 'id', 'username', 'snippets')
|
||||||
|
|
||||||
Notice that we've also added a new `'highlight'` field. This field is of the same type as the `url` field, except that it points to the `'snippet-highlight'` url pattern, instead of the `'snippet-detail'` url pattern.
|
Notice that we've also added a new `'highlight'` field. This field is of the same type as the `url` field, except that it points to the `'snippet-highlight'` url pattern, instead of the `'snippet-detail'` url pattern.
|
||||||
|
|
||||||
|
@ -116,16 +116,16 @@ After adding all those names into our URLconf, our final `snippets/urls.py` file
|
||||||
url(r'^snippets/$',
|
url(r'^snippets/$',
|
||||||
views.SnippetList.as_view(),
|
views.SnippetList.as_view(),
|
||||||
name='snippet-list'),
|
name='snippet-list'),
|
||||||
url(r'^snippets/(?P<pk>[0-9]+)/$',
|
url(r'^snippets/(?P<id>[0-9]+)/$',
|
||||||
views.SnippetDetail.as_view(),
|
views.SnippetDetail.as_view(),
|
||||||
name='snippet-detail'),
|
name='snippet-detail'),
|
||||||
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
|
url(r'^snippets/(?P<id>[0-9]+)/highlight/$',
|
||||||
views.SnippetHighlight.as_view(),
|
views.SnippetHighlight.as_view(),
|
||||||
name='snippet-highlight'),
|
name='snippet-highlight'),
|
||||||
url(r'^users/$',
|
url(r'^users/$',
|
||||||
views.UserList.as_view(),
|
views.UserList.as_view(),
|
||||||
name='user-list'),
|
name='user-list'),
|
||||||
url(r'^users/(?P<pk>[0-9]+)/$',
|
url(r'^users/(?P<id>[0-9]+)/$',
|
||||||
views.UserDetail.as_view(),
|
views.UserDetail.as_view(),
|
||||||
name='user-detail')
|
name='user-detail')
|
||||||
])
|
])
|
||||||
|
|
|
@ -92,10 +92,10 @@ Now that we've bound our resources into concrete views, we can register the view
|
||||||
urlpatterns = format_suffix_patterns([
|
urlpatterns = format_suffix_patterns([
|
||||||
url(r'^$', api_root),
|
url(r'^$', api_root),
|
||||||
url(r'^snippets/$', snippet_list, name='snippet-list'),
|
url(r'^snippets/$', snippet_list, name='snippet-list'),
|
||||||
url(r'^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'),
|
url(r'^snippets/(?P<id>[0-9]+)/$', snippet_detail, name='snippet-detail'),
|
||||||
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
|
url(r'^snippets/(?P<id>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
|
||||||
url(r'^users/$', user_list, name='user-list'),
|
url(r'^users/$', user_list, name='user-list'),
|
||||||
url(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail')
|
url(r'^users/(?P<id>[0-9]+)/$', user_detail, name='user-detail')
|
||||||
])
|
])
|
||||||
|
|
||||||
## Using Routers
|
## Using Routers
|
||||||
|
|
|
@ -101,13 +101,13 @@ First we'll load the API schema using the command line client.
|
||||||
$ coreapi get http://127.0.0.1:8000/schema/
|
$ coreapi get http://127.0.0.1:8000/schema/
|
||||||
<Pastebin API "http://127.0.0.1:8000/schema/">
|
<Pastebin API "http://127.0.0.1:8000/schema/">
|
||||||
snippets: {
|
snippets: {
|
||||||
highlight(pk)
|
highlight(id)
|
||||||
list()
|
list()
|
||||||
retrieve(pk)
|
read(id)
|
||||||
}
|
}
|
||||||
users: {
|
users: {
|
||||||
list()
|
list()
|
||||||
retrieve(pk)
|
read(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
We haven't authenticated yet, so right now we're only able to see the read only
|
We haven't authenticated yet, so right now we're only able to see the read only
|
||||||
|
@ -119,7 +119,7 @@ Let's try listing the existing snippets, using the command line client:
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"url": "http://127.0.0.1:8000/snippets/1/",
|
"url": "http://127.0.0.1:8000/snippets/1/",
|
||||||
"pk": 1,
|
"id": 1,
|
||||||
"highlight": "http://127.0.0.1:8000/snippets/1/highlight/",
|
"highlight": "http://127.0.0.1:8000/snippets/1/highlight/",
|
||||||
"owner": "lucy",
|
"owner": "lucy",
|
||||||
"title": "Example",
|
"title": "Example",
|
||||||
|
@ -133,7 +133,7 @@ Let's try listing the existing snippets, using the command line client:
|
||||||
Some of the API endpoints require named parameters. For example, to get back
|
Some of the API endpoints require named parameters. For example, to get back
|
||||||
the highlight HTML for a particular snippet we need to provide an id.
|
the highlight HTML for a particular snippet we need to provide an id.
|
||||||
|
|
||||||
$ coreapi action snippets highlight --param pk=1
|
$ coreapi action snippets highlight --param id=1
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
|
@ -160,16 +160,16 @@ set of available interactions.
|
||||||
Pastebin API "http://127.0.0.1:8000/schema/">
|
Pastebin API "http://127.0.0.1:8000/schema/">
|
||||||
snippets: {
|
snippets: {
|
||||||
create(code, [title], [linenos], [language], [style])
|
create(code, [title], [linenos], [language], [style])
|
||||||
destroy(pk)
|
delete(id)
|
||||||
highlight(pk)
|
highlight(id)
|
||||||
list()
|
list()
|
||||||
partial_update(pk, [title], [code], [linenos], [language], [style])
|
partial_update(id, [title], [code], [linenos], [language], [style])
|
||||||
retrieve(pk)
|
read(id)
|
||||||
update(pk, code, [title], [linenos], [language], [style])
|
update(id, code, [title], [linenos], [language], [style])
|
||||||
}
|
}
|
||||||
users: {
|
users: {
|
||||||
list()
|
list()
|
||||||
retrieve(pk)
|
read(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
We're now able to interact with these endpoints. For example, to create a new
|
We're now able to interact with these endpoints. For example, to create a new
|
||||||
|
@ -178,7 +178,7 @@ snippet:
|
||||||
$ coreapi action snippets create --param title="Example" --param code="print('hello, world')"
|
$ coreapi action snippets create --param title="Example" --param code="print('hello, world')"
|
||||||
{
|
{
|
||||||
"url": "http://127.0.0.1:8000/snippets/7/",
|
"url": "http://127.0.0.1:8000/snippets/7/",
|
||||||
"pk": 7,
|
"id": 7,
|
||||||
"highlight": "http://127.0.0.1:8000/snippets/7/highlight/",
|
"highlight": "http://127.0.0.1:8000/snippets/7/highlight/",
|
||||||
"owner": "lucy",
|
"owner": "lucy",
|
||||||
"title": "Example",
|
"title": "Example",
|
||||||
|
@ -190,7 +190,7 @@ snippet:
|
||||||
|
|
||||||
And to delete a snippet:
|
And to delete a snippet:
|
||||||
|
|
||||||
$ coreapi action snippets destroy --param pk=7
|
$ coreapi action snippets delete --param id=7
|
||||||
|
|
||||||
As well as the command line client, developers can also interact with your
|
As well as the command line client, developers can also interact with your
|
||||||
API using client libraries. The Python client library is the first of these
|
API using client libraries. The Python client library is the first of these
|
||||||
|
|
|
@ -194,7 +194,7 @@ class EndpointInspector(object):
|
||||||
|
|
||||||
|
|
||||||
class SchemaGenerator(object):
|
class SchemaGenerator(object):
|
||||||
# Map methods onto 'actions' that are the names used in the link layout.
|
# Map HTTP methods onto actions.
|
||||||
default_mapping = {
|
default_mapping = {
|
||||||
'get': 'retrieve',
|
'get': 'retrieve',
|
||||||
'post': 'create',
|
'post': 'create',
|
||||||
|
@ -203,9 +203,16 @@ class SchemaGenerator(object):
|
||||||
'delete': 'destroy',
|
'delete': 'destroy',
|
||||||
}
|
}
|
||||||
endpoint_inspector_cls = EndpointInspector
|
endpoint_inspector_cls = EndpointInspector
|
||||||
|
|
||||||
|
# Map the method names we use for viewset actions onto external schema names.
|
||||||
|
# These give us names that are more suitable for the external representation.
|
||||||
|
# Set by 'SCHEMA_COERCE_METHOD_NAMES'.
|
||||||
|
coerce_method_names = None
|
||||||
|
|
||||||
# 'pk' isn't great as an externally exposed name for an identifier,
|
# 'pk' isn't great as an externally exposed name for an identifier,
|
||||||
# so by default we prefer to use the actual model field name for schemas.
|
# so by default we prefer to use the actual model field name for schemas.
|
||||||
coerce_pk = True
|
# Set by 'SCHEMA_COERCE_PATH_PK'.
|
||||||
|
coerce_path_pk = None
|
||||||
|
|
||||||
def __init__(self, title=None, url=None, patterns=None, urlconf=None):
|
def __init__(self, title=None, url=None, patterns=None, urlconf=None):
|
||||||
assert coreapi, '`coreapi` must be installed for schema support.'
|
assert coreapi, '`coreapi` must be installed for schema support.'
|
||||||
|
@ -213,6 +220,9 @@ class SchemaGenerator(object):
|
||||||
if url and not url.endswith('/'):
|
if url and not url.endswith('/'):
|
||||||
url += '/'
|
url += '/'
|
||||||
|
|
||||||
|
self.coerce_method_names = api_settings.SCHEMA_COERCE_METHOD_NAMES
|
||||||
|
self.coerce_path_pk = api_settings.SCHEMA_COERCE_PATH_PK
|
||||||
|
|
||||||
self.patterns = patterns
|
self.patterns = patterns
|
||||||
self.urlconf = urlconf
|
self.urlconf = urlconf
|
||||||
self.title = title
|
self.title = title
|
||||||
|
@ -339,7 +349,7 @@ class SchemaGenerator(object):
|
||||||
where possible. This is cleaner for an external representation.
|
where possible. This is cleaner for an external representation.
|
||||||
(Ie. "this is an identifier", not "this is a database primary key")
|
(Ie. "this is an identifier", not "this is a database primary key")
|
||||||
"""
|
"""
|
||||||
if not self.coerce_pk or '{pk}' not in path:
|
if not self.coerce_path_pk or '{pk}' not in path:
|
||||||
return path
|
return path
|
||||||
model = getattr(getattr(view, 'queryset', None), 'model', None)
|
model = getattr(getattr(view, 'queryset', None), 'model', None)
|
||||||
if model:
|
if model:
|
||||||
|
@ -405,6 +415,9 @@ class SchemaGenerator(object):
|
||||||
header = getattr(view, 'action', method.lower())
|
header = getattr(view, 'action', method.lower())
|
||||||
if header in sections:
|
if header in sections:
|
||||||
return sections[header].strip()
|
return sections[header].strip()
|
||||||
|
if header in self.coerce_method_names:
|
||||||
|
if self.coerce_method_names[header] in sections:
|
||||||
|
return sections[self.coerce_method_names[header]].strip()
|
||||||
return sections[''].strip()
|
return sections[''].strip()
|
||||||
|
|
||||||
def get_encoding(self, path, method, view):
|
def get_encoding(self, path, method, view):
|
||||||
|
@ -541,10 +554,15 @@ class SchemaGenerator(object):
|
||||||
# Custom action, eg "/users/{pk}/activate/", "/users/active/"
|
# Custom action, eg "/users/{pk}/activate/", "/users/active/"
|
||||||
if len(view.action_map) > 1:
|
if len(view.action_map) > 1:
|
||||||
action = self.default_mapping[method.lower()]
|
action = self.default_mapping[method.lower()]
|
||||||
|
if action in self.coerce_method_names:
|
||||||
|
action = self.coerce_method_names[action]
|
||||||
return named_path_components + [action]
|
return named_path_components + [action]
|
||||||
else:
|
else:
|
||||||
return named_path_components[:-1] + [action]
|
return named_path_components[:-1] + [action]
|
||||||
|
|
||||||
|
if action in self.coerce_method_names:
|
||||||
|
action = self.coerce_method_names[action]
|
||||||
|
|
||||||
# Default action, eg "/users/", "/users/{pk}/"
|
# Default action, eg "/users/", "/users/{pk}/"
|
||||||
return named_path_components + [action]
|
return named_path_components + [action]
|
||||||
|
|
||||||
|
|
|
@ -115,6 +115,13 @@ DEFAULTS = {
|
||||||
# Browseable API
|
# Browseable API
|
||||||
'HTML_SELECT_CUTOFF': 1000,
|
'HTML_SELECT_CUTOFF': 1000,
|
||||||
'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...",
|
'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...",
|
||||||
|
|
||||||
|
# Schemas
|
||||||
|
'SCHEMA_COERCE_PATH_PK': True,
|
||||||
|
'SCHEMA_COERCE_METHOD_NAMES': {
|
||||||
|
'retrieve': 'read',
|
||||||
|
'destroy': 'delete'
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -104,12 +104,12 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
action='get'
|
action='get'
|
||||||
),
|
),
|
||||||
'custom_list_action_multiple_methods': {
|
'custom_list_action_multiple_methods': {
|
||||||
'retrieve': coreapi.Link(
|
'read': coreapi.Link(
|
||||||
url='/example/custom_list_action_multiple_methods/',
|
url='/example/custom_list_action_multiple_methods/',
|
||||||
action='get'
|
action='get'
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'retrieve': coreapi.Link(
|
'read': coreapi.Link(
|
||||||
url='/example/{id}/',
|
url='/example/{id}/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -148,7 +148,7 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
coreapi.Field('b', required=False, location='form', type='string')
|
coreapi.Field('b', required=False, location='form', type='string')
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'retrieve': coreapi.Link(
|
'read': coreapi.Link(
|
||||||
url='/example/{id}/',
|
url='/example/{id}/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -171,7 +171,7 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
action='get'
|
action='get'
|
||||||
),
|
),
|
||||||
'custom_list_action_multiple_methods': {
|
'custom_list_action_multiple_methods': {
|
||||||
'retrieve': coreapi.Link(
|
'read': coreapi.Link(
|
||||||
url='/example/custom_list_action_multiple_methods/',
|
url='/example/custom_list_action_multiple_methods/',
|
||||||
action='get'
|
action='get'
|
||||||
),
|
),
|
||||||
|
@ -200,7 +200,7 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
coreapi.Field('b', required=False, location='form', type='string')
|
coreapi.Field('b', required=False, location='form', type='string')
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'destroy': coreapi.Link(
|
'delete': coreapi.Link(
|
||||||
url='/example/{id}/',
|
url='/example/{id}/',
|
||||||
action='delete',
|
action='delete',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -260,7 +260,7 @@ class TestSchemaGenerator(TestCase):
|
||||||
action='get',
|
action='get',
|
||||||
fields=[]
|
fields=[]
|
||||||
),
|
),
|
||||||
'retrieve': coreapi.Link(
|
'read': coreapi.Link(
|
||||||
url='/example/{id}/',
|
url='/example/{id}/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -313,7 +313,7 @@ class TestSchemaGeneratorNotAtRoot(TestCase):
|
||||||
action='get',
|
action='get',
|
||||||
fields=[]
|
fields=[]
|
||||||
),
|
),
|
||||||
'retrieve': coreapi.Link(
|
'read': coreapi.Link(
|
||||||
url='/api/v1/example/{id}/',
|
url='/api/v1/example/{id}/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
|
Loading…
Reference in New Issue
Block a user