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
|
||||
|
||||
#### 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):
|
||||
pk = serializers.IntegerField(read_only=True)
|
||||
id = serializers.IntegerField(read_only=True)
|
||||
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
|
||||
code = serializers.CharField(style={'base_template': 'textarea.html'})
|
||||
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.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`.
|
||||
|
||||
content = JSONRenderer().render(serializer.data)
|
||||
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...
|
||||
|
||||
|
@ -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.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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
@csrf_exempt
|
||||
def snippet_detail(request, pk):
|
||||
def snippet_detail(request, id):
|
||||
"""
|
||||
Retrieve, update or delete a code snippet.
|
||||
"""
|
||||
try:
|
||||
snippet = Snippet.objects.get(pk=pk)
|
||||
snippet = Snippet.objects.get(id=id)
|
||||
except Snippet.DoesNotExist:
|
||||
return HttpResponse(status=404)
|
||||
|
||||
|
@ -291,7 +291,7 @@ Finally we need to wire these views up. Create the `snippets/urls.py` file:
|
|||
|
||||
urlpatterns = [
|
||||
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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
@api_view(['GET', 'PUT', 'DELETE'])
|
||||
def snippet_detail(request, pk):
|
||||
def snippet_detail(request, id):
|
||||
"""
|
||||
Retrieve, update or delete a snippet instance.
|
||||
"""
|
||||
try:
|
||||
snippet = Snippet.objects.get(pk=pk)
|
||||
snippet = Snippet.objects.get(id=id)
|
||||
except Snippet.DoesNotExist:
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
|
@ -114,7 +114,7 @@ Now update the `urls.py` file slightly, to append a set of `format_suffix_patter
|
|||
|
||||
urlpatterns = [
|
||||
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)
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
def get_object(self, pk):
|
||||
def get_object(self, id):
|
||||
try:
|
||||
return Snippet.objects.get(pk=pk)
|
||||
return Snippet.objects.get(id=id)
|
||||
except Snippet.DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
def get(self, request, pk, format=None):
|
||||
snippet = self.get_object(pk)
|
||||
def get(self, request, id, format=None):
|
||||
snippet = self.get_object(id)
|
||||
serializer = SnippetSerializer(snippet)
|
||||
return Response(serializer.data)
|
||||
|
||||
def put(self, request, pk, format=None):
|
||||
snippet = self.get_object(pk)
|
||||
def put(self, request, id, format=None):
|
||||
snippet = self.get_object(id)
|
||||
serializer = SnippetSerializer(snippet, data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def delete(self, request, pk, format=None):
|
||||
snippet = self.get_object(pk)
|
||||
def delete(self, request, id, format=None):
|
||||
snippet = self.get_object(id)
|
||||
snippet.delete()
|
||||
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 = [
|
||||
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)
|
||||
|
|
|
@ -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`.
|
||||
|
||||
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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
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
|
||||
|
||||
|
@ -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`:
|
||||
|
||||
* 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`.
|
||||
* Relationships use `HyperlinkedRelatedField`,
|
||||
instead of `PrimaryKeyRelatedField`.
|
||||
|
@ -80,7 +80,7 @@ We can easily re-write our existing serializers to use hyperlinking. In your `sn
|
|||
|
||||
class Meta:
|
||||
model = Snippet
|
||||
fields = ('url', 'pk', 'highlight', 'owner',
|
||||
fields = ('url', 'id', 'highlight', 'owner',
|
||||
'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:
|
||||
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.
|
||||
|
||||
|
@ -116,16 +116,16 @@ After adding all those names into our URLconf, our final `snippets/urls.py` file
|
|||
url(r'^snippets/$',
|
||||
views.SnippetList.as_view(),
|
||||
name='snippet-list'),
|
||||
url(r'^snippets/(?P<pk>[0-9]+)/$',
|
||||
url(r'^snippets/(?P<id>[0-9]+)/$',
|
||||
views.SnippetDetail.as_view(),
|
||||
name='snippet-detail'),
|
||||
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
|
||||
url(r'^snippets/(?P<id>[0-9]+)/highlight/$',
|
||||
views.SnippetHighlight.as_view(),
|
||||
name='snippet-highlight'),
|
||||
url(r'^users/$',
|
||||
views.UserList.as_view(),
|
||||
name='user-list'),
|
||||
url(r'^users/(?P<pk>[0-9]+)/$',
|
||||
url(r'^users/(?P<id>[0-9]+)/$',
|
||||
views.UserDetail.as_view(),
|
||||
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([
|
||||
url(r'^$', api_root),
|
||||
url(r'^snippets/$', snippet_list, name='snippet-list'),
|
||||
url(r'^snippets/(?P<pk>[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]+)/$', snippet_detail, name='snippet-detail'),
|
||||
url(r'^snippets/(?P<id>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
|
||||
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
|
||||
|
|
|
@ -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/
|
||||
<Pastebin API "http://127.0.0.1:8000/schema/">
|
||||
snippets: {
|
||||
highlight(pk)
|
||||
highlight(id)
|
||||
list()
|
||||
retrieve(pk)
|
||||
read(id)
|
||||
}
|
||||
users: {
|
||||
list()
|
||||
retrieve(pk)
|
||||
read(id)
|
||||
}
|
||||
|
||||
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/",
|
||||
"pk": 1,
|
||||
"id": 1,
|
||||
"highlight": "http://127.0.0.1:8000/snippets/1/highlight/",
|
||||
"owner": "lucy",
|
||||
"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
|
||||
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">
|
||||
|
||||
<html>
|
||||
|
@ -160,16 +160,16 @@ set of available interactions.
|
|||
Pastebin API "http://127.0.0.1:8000/schema/">
|
||||
snippets: {
|
||||
create(code, [title], [linenos], [language], [style])
|
||||
destroy(pk)
|
||||
highlight(pk)
|
||||
delete(id)
|
||||
highlight(id)
|
||||
list()
|
||||
partial_update(pk, [title], [code], [linenos], [language], [style])
|
||||
retrieve(pk)
|
||||
update(pk, code, [title], [linenos], [language], [style])
|
||||
partial_update(id, [title], [code], [linenos], [language], [style])
|
||||
read(id)
|
||||
update(id, code, [title], [linenos], [language], [style])
|
||||
}
|
||||
users: {
|
||||
list()
|
||||
retrieve(pk)
|
||||
read(id)
|
||||
}
|
||||
|
||||
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')"
|
||||
{
|
||||
"url": "http://127.0.0.1:8000/snippets/7/",
|
||||
"pk": 7,
|
||||
"id": 7,
|
||||
"highlight": "http://127.0.0.1:8000/snippets/7/highlight/",
|
||||
"owner": "lucy",
|
||||
"title": "Example",
|
||||
|
@ -190,7 +190,7 @@ 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
|
||||
API using client libraries. The Python client library is the first of these
|
||||
|
|
|
@ -194,7 +194,7 @@ class EndpointInspector(object):
|
|||
|
||||
|
||||
class SchemaGenerator(object):
|
||||
# Map methods onto 'actions' that are the names used in the link layout.
|
||||
# Map HTTP methods onto actions.
|
||||
default_mapping = {
|
||||
'get': 'retrieve',
|
||||
'post': 'create',
|
||||
|
@ -203,9 +203,16 @@ class SchemaGenerator(object):
|
|||
'delete': 'destroy',
|
||||
}
|
||||
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,
|
||||
# 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):
|
||||
assert coreapi, '`coreapi` must be installed for schema support.'
|
||||
|
@ -213,6 +220,9 @@ class SchemaGenerator(object):
|
|||
if url and not url.endswith('/'):
|
||||
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.urlconf = urlconf
|
||||
self.title = title
|
||||
|
@ -339,7 +349,7 @@ class SchemaGenerator(object):
|
|||
where possible. This is cleaner for an external representation.
|
||||
(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
|
||||
model = getattr(getattr(view, 'queryset', None), 'model', None)
|
||||
if model:
|
||||
|
@ -405,6 +415,9 @@ class SchemaGenerator(object):
|
|||
header = getattr(view, 'action', method.lower())
|
||||
if header in sections:
|
||||
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()
|
||||
|
||||
def get_encoding(self, path, method, view):
|
||||
|
@ -541,10 +554,15 @@ class SchemaGenerator(object):
|
|||
# Custom action, eg "/users/{pk}/activate/", "/users/active/"
|
||||
if len(view.action_map) > 1:
|
||||
action = self.default_mapping[method.lower()]
|
||||
if action in self.coerce_method_names:
|
||||
action = self.coerce_method_names[action]
|
||||
return named_path_components + [action]
|
||||
else:
|
||||
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}/"
|
||||
return named_path_components + [action]
|
||||
|
||||
|
|
|
@ -115,6 +115,13 @@ DEFAULTS = {
|
|||
# Browseable API
|
||||
'HTML_SELECT_CUTOFF': 1000,
|
||||
'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'
|
||||
),
|
||||
'custom_list_action_multiple_methods': {
|
||||
'retrieve': coreapi.Link(
|
||||
'read': coreapi.Link(
|
||||
url='/example/custom_list_action_multiple_methods/',
|
||||
action='get'
|
||||
)
|
||||
},
|
||||
'retrieve': coreapi.Link(
|
||||
'read': coreapi.Link(
|
||||
url='/example/{id}/',
|
||||
action='get',
|
||||
fields=[
|
||||
|
@ -148,7 +148,7 @@ class TestRouterGeneratedSchema(TestCase):
|
|||
coreapi.Field('b', required=False, location='form', type='string')
|
||||
]
|
||||
),
|
||||
'retrieve': coreapi.Link(
|
||||
'read': coreapi.Link(
|
||||
url='/example/{id}/',
|
||||
action='get',
|
||||
fields=[
|
||||
|
@ -171,7 +171,7 @@ class TestRouterGeneratedSchema(TestCase):
|
|||
action='get'
|
||||
),
|
||||
'custom_list_action_multiple_methods': {
|
||||
'retrieve': coreapi.Link(
|
||||
'read': coreapi.Link(
|
||||
url='/example/custom_list_action_multiple_methods/',
|
||||
action='get'
|
||||
),
|
||||
|
@ -200,7 +200,7 @@ class TestRouterGeneratedSchema(TestCase):
|
|||
coreapi.Field('b', required=False, location='form', type='string')
|
||||
]
|
||||
),
|
||||
'destroy': coreapi.Link(
|
||||
'delete': coreapi.Link(
|
||||
url='/example/{id}/',
|
||||
action='delete',
|
||||
fields=[
|
||||
|
@ -260,7 +260,7 @@ class TestSchemaGenerator(TestCase):
|
|||
action='get',
|
||||
fields=[]
|
||||
),
|
||||
'retrieve': coreapi.Link(
|
||||
'read': coreapi.Link(
|
||||
url='/example/{id}/',
|
||||
action='get',
|
||||
fields=[
|
||||
|
@ -313,7 +313,7 @@ class TestSchemaGeneratorNotAtRoot(TestCase):
|
|||
action='get',
|
||||
fields=[]
|
||||
),
|
||||
'retrieve': coreapi.Link(
|
||||
'read': coreapi.Link(
|
||||
url='/api/v1/example/{id}/',
|
||||
action='get',
|
||||
fields=[
|
||||
|
|
Loading…
Reference in New Issue
Block a user