mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-07 22:04:48 +03:00
Initial schema generation & first tutorial 7 draft
This commit is contained in:
parent
6c60f58a56
commit
b7fcdd257e
|
@ -130,27 +130,7 @@ Using viewsets can be a really useful abstraction. It helps ensure that URL con
|
||||||
|
|
||||||
That doesn't mean it's always the right approach to take. There's a similar set of trade-offs to consider as when using class-based views instead of function based views. Using viewsets is less explicit than building your views individually.
|
That doesn't mean it's always the right approach to take. There's a similar set of trade-offs to consider as when using class-based views instead of function based views. Using viewsets is less explicit than building your views individually.
|
||||||
|
|
||||||
## Reviewing our work
|
In [part 7][tut-7] of the tutorial we'll look at how we can add an API schema,
|
||||||
|
and interact with our API using a client library or command line tool.
|
||||||
|
|
||||||
With an incredibly small amount of code, we've now got a complete pastebin Web API, which is fully web browsable, and comes complete with authentication, per-object permissions, and multiple renderer formats.
|
[tut-7]: 7-schemas-and-client-libraries.md
|
||||||
|
|
||||||
We've walked through each step of the design process, and seen how if we need to customize anything we can gradually work our way down to simply using regular Django views.
|
|
||||||
|
|
||||||
You can review the final [tutorial code][repo] on GitHub, or try out a live example in [the sandbox][sandbox].
|
|
||||||
|
|
||||||
## Onwards and upwards
|
|
||||||
|
|
||||||
We've reached the end of our tutorial. If you want to get more involved in the REST framework project, here are a few places you can start:
|
|
||||||
|
|
||||||
* Contribute on [GitHub][github] by reviewing and submitting issues, and making pull requests.
|
|
||||||
* Join the [REST framework discussion group][group], and help build the community.
|
|
||||||
* Follow [the author][twitter] on Twitter and say hi.
|
|
||||||
|
|
||||||
**Now go build awesome things.**
|
|
||||||
|
|
||||||
|
|
||||||
[repo]: https://github.com/tomchristie/rest-framework-tutorial
|
|
||||||
[sandbox]: http://restframework.herokuapp.com/
|
|
||||||
[github]: https://github.com/tomchristie/django-rest-framework
|
|
||||||
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
|
|
||||||
[twitter]: https://twitter.com/_tomchristie
|
|
||||||
|
|
|
@ -90,45 +90,127 @@ First we'll load the API schema using the command line client.
|
||||||
$ coreapi get http://127.0.0.1:8000/
|
$ coreapi get http://127.0.0.1:8000/
|
||||||
<Pastebin API "http://127.0.0.1:8000/">
|
<Pastebin API "http://127.0.0.1:8000/">
|
||||||
snippets: {
|
snippets: {
|
||||||
create(code, [title], [linenos], [language], [style])
|
highlight(pk)
|
||||||
destroy(id)
|
|
||||||
highlight(id)
|
|
||||||
list()
|
list()
|
||||||
partial_update(id, [title], [code], [linenos], [language], [style])
|
retrieve(pk)
|
||||||
retrieve(id)
|
|
||||||
update(id, code, [title], [linenos], [language], [style])
|
|
||||||
}
|
}
|
||||||
users: {
|
users: {
|
||||||
list()
|
list()
|
||||||
retrieve(id)
|
retrieve(pk)
|
||||||
}
|
}
|
||||||
|
|
||||||
At this point we're able to see all the available API endpoints.
|
We haven't authenticated yet, so right now we're only able to see the read only
|
||||||
|
endpoints, in line with how we've set up the permissions on the API.
|
||||||
|
|
||||||
We can now interact with the API using the command line client:
|
Let's try listing the existing snippets, using the command line client:
|
||||||
|
|
||||||
$ coreapi action list_snippets
|
$ coreapi action snippets list
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"url": "http://127.0.0.1:8000/snippets/1/",
|
||||||
|
"highlight": "http://127.0.0.1:8000/snippets/1/highlight/",
|
||||||
|
"owner": "lucy",
|
||||||
|
"title": "Example",
|
||||||
|
"code": "print('hello, world!')",
|
||||||
|
"linenos": true,
|
||||||
|
"language": "python",
|
||||||
|
"style": "friendly"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
|
||||||
|
Some of the API endpoints require named parameters. For example, to get back
|
||||||
|
the hightlight HTML for a particular snippet we need to provide an id.
|
||||||
|
|
||||||
|
$ coreapi action snippets highlight --param pk 1
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Example</title>
|
||||||
|
...
|
||||||
|
|
||||||
## Authenticating our client
|
## Authenticating our client
|
||||||
|
|
||||||
TODO - authentication
|
If we want to be able to create and edit snippets, we'll need to authenticate
|
||||||
|
as a valid user. In this case we'll just use basic auth.
|
||||||
|
|
||||||
$ coreapi action snippets create --param code "print('hello, world')"
|
Make sure to replace the `<username>` and `<password>` below with your
|
||||||
|
actual username and password.
|
||||||
|
|
||||||
$ coreapi credentials add 127.0.0.1 <username>:<password> --auth basic
|
$ coreapi credentials add 127.0.0.1 <username>:<password> --auth basic
|
||||||
|
Added credentials
|
||||||
|
127.0.0.1 "Basic <...>"
|
||||||
|
|
||||||
## Using a client library
|
Now if we fetch the schema again, we should be able to see the full
|
||||||
|
set of available interactions.
|
||||||
|
|
||||||
*TODO - document using python client library, rather than the command line tool.*
|
$ coreapi reload
|
||||||
|
Pastebin API "http://127.0.0.1:8000/">
|
||||||
|
snippets: {
|
||||||
|
create(code, [title], [linenos], [language], [style])
|
||||||
|
destroy(pk)
|
||||||
|
highlight(pk)
|
||||||
|
list()
|
||||||
|
partial_update(pk, [title], [code], [linenos], [language], [style])
|
||||||
|
retrieve(pk)
|
||||||
|
update(pk, code, [title], [linenos], [language], [style])
|
||||||
|
}
|
||||||
|
users: {
|
||||||
|
list()
|
||||||
|
retrieve(pk)
|
||||||
|
}
|
||||||
|
|
||||||
## Using another schema format
|
We're now able to interact with these endpoints. For example, to create a new
|
||||||
|
snippet:
|
||||||
|
|
||||||
*TODO - document using OpenAPI instead.*
|
$ coreapi action snippets create --param title "Example" --param code "print('hello, world')"
|
||||||
|
{
|
||||||
|
"url": "http://127.0.0.1:8000/snippets/7/",
|
||||||
|
"id": 7,
|
||||||
|
"highlight": "http://127.0.0.1:8000/snippets/7/highlight/",
|
||||||
|
"owner": "lucy",
|
||||||
|
"title": "Example",
|
||||||
|
"code": "print('hello, world')",
|
||||||
|
"linenos": false,
|
||||||
|
"language": "python",
|
||||||
|
"style": "friendly"
|
||||||
|
}
|
||||||
|
|
||||||
## Customizing schema generation
|
And to delete a snippet:
|
||||||
|
|
||||||
*TODO - document writing an explict schema view.*
|
$ coreapi action snippets destroy --param pk 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
|
||||||
|
to be available, and a Javascript client library is planned to be released
|
||||||
|
soon.
|
||||||
|
|
||||||
|
For more details on customizing schema generation and using Core API
|
||||||
|
client libraries you'll need to refer to the full documentation.
|
||||||
|
|
||||||
|
## Reviewing our work
|
||||||
|
|
||||||
|
With an incredibly small amount of code, we've now got a complete pastebin Web API, which is fully web browsable, includes a schema-driven client library, and comes complete with authentication, per-object permissions, and multiple renderer formats.
|
||||||
|
|
||||||
|
We've walked through each step of the design process, and seen how if we need to customize anything we can gradually work our way down to simply using regular Django views.
|
||||||
|
|
||||||
|
You can review the final [tutorial code][repo] on GitHub, or try out a live example in [the sandbox][sandbox].
|
||||||
|
|
||||||
|
## Onwards and upwards
|
||||||
|
|
||||||
|
We've reached the end of our tutorial. If you want to get more involved in the REST framework project, here are a few places you can start:
|
||||||
|
|
||||||
|
* Contribute on [GitHub][github] by reviewing and submitting issues, and making pull requests.
|
||||||
|
* Join the [REST framework discussion group][group], and help build the community.
|
||||||
|
* Follow [the author][twitter] on Twitter and say hi.
|
||||||
|
|
||||||
|
**Now go build awesome things.**
|
||||||
|
|
||||||
[coreapi]: http://www.coreapi.org
|
[coreapi]: http://www.coreapi.org
|
||||||
[corejson]: http://www.coreapi.org/specification/encoding/#core-json-encoding
|
[corejson]: http://www.coreapi.org/specification/encoding/#core-json-encoding
|
||||||
[openapi]: https://openapis.org/
|
[openapi]: https://openapis.org/
|
||||||
|
[repo]: https://github.com/tomchristie/rest-framework-tutorial
|
||||||
|
[sandbox]: http://restframework.herokuapp.com/
|
||||||
|
[github]: https://github.com/tomchristie/django-rest-framework
|
||||||
|
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
|
||||||
|
[twitter]: https://twitter.com/_tomchristie
|
||||||
|
|
|
@ -23,8 +23,9 @@ from django.conf.urls import url
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.urlresolvers import NoReverseMatch
|
from django.core.urlresolvers import NoReverseMatch
|
||||||
|
|
||||||
from rest_framework import renderers, views
|
from rest_framework import exceptions, renderers, views
|
||||||
from rest_framework.compat import coreapi
|
from rest_framework.compat import coreapi
|
||||||
|
from rest_framework.request import override_method
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.reverse import reverse
|
from rest_framework.reverse import reverse
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
|
@ -262,7 +263,7 @@ class SimpleRouter(BaseRouter):
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_links(self):
|
def get_links(self, request=None):
|
||||||
content = {}
|
content = {}
|
||||||
|
|
||||||
for prefix, viewset, basename in self.registry:
|
for prefix, viewset, basename in self.registry:
|
||||||
|
@ -284,13 +285,23 @@ class SimpleRouter(BaseRouter):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for method, action in mapping.items():
|
for method, action in mapping.items():
|
||||||
|
link = self.get_link(viewset, url, method, request)
|
||||||
|
if link is None:
|
||||||
|
continue # User does not have permissions.
|
||||||
if prefix not in content:
|
if prefix not in content:
|
||||||
content[prefix] = {}
|
content[prefix] = {}
|
||||||
link = self.get_link(viewset, url, method)
|
|
||||||
content[prefix][action] = link
|
content[prefix][action] = link
|
||||||
return content
|
return content
|
||||||
|
|
||||||
def get_link(self, viewset, url, method):
|
def get_link(self, viewset, url, method, request=None):
|
||||||
|
view_instance = viewset()
|
||||||
|
if request is not None:
|
||||||
|
with override_method(view_instance, request, method.upper()) as request:
|
||||||
|
try:
|
||||||
|
view_instance.check_permissions(request)
|
||||||
|
except exceptions.APIException as exc:
|
||||||
|
return None
|
||||||
|
|
||||||
fields = []
|
fields = []
|
||||||
|
|
||||||
for variable in uritemplate.variables(url):
|
for variable in uritemplate.variables(url):
|
||||||
|
@ -298,7 +309,7 @@ class SimpleRouter(BaseRouter):
|
||||||
fields.append(field)
|
fields.append(field)
|
||||||
|
|
||||||
if method in ('put', 'patch', 'post'):
|
if method in ('put', 'patch', 'post'):
|
||||||
cls = viewset().get_serializer_class()
|
cls = view_instance.get_serializer_class()
|
||||||
serializer = cls()
|
serializer = cls()
|
||||||
for field in serializer.fields.values():
|
for field in serializer.fields.values():
|
||||||
if field.read_only:
|
if field.read_only:
|
||||||
|
@ -336,9 +347,8 @@ class DefaultRouter(SimpleRouter):
|
||||||
|
|
||||||
if self.schema_title:
|
if self.schema_title:
|
||||||
assert coreapi, '`coreapi` must be installed for schema support.'
|
assert coreapi, '`coreapi` must be installed for schema support.'
|
||||||
content = self.get_links()
|
|
||||||
schema = coreapi.Document(title=self.schema_title, content=content)
|
|
||||||
view_renderers += [renderers.CoreJSONRenderer]
|
view_renderers += [renderers.CoreJSONRenderer]
|
||||||
|
router = self
|
||||||
|
|
||||||
class APIRoot(views.APIView):
|
class APIRoot(views.APIView):
|
||||||
_ignore_model_permissions = True
|
_ignore_model_permissions = True
|
||||||
|
@ -346,6 +356,10 @@ class DefaultRouter(SimpleRouter):
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
if request.accepted_renderer.format == 'corejson':
|
if request.accepted_renderer.format == 'corejson':
|
||||||
|
content = router.get_links(request)
|
||||||
|
if not content:
|
||||||
|
raise exceptions.PermissionDenied()
|
||||||
|
schema = coreapi.Document(title=router.schema_title, content=content)
|
||||||
return Response(schema)
|
return Response(schema)
|
||||||
|
|
||||||
ret = OrderedDict()
|
ret = OrderedDict()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user