Initial schema generation & first tutorial 7 draft

This commit is contained in:
Tom Christie 2016-06-10 11:09:16 +01:00
parent 6c60f58a56
commit b7fcdd257e
3 changed files with 124 additions and 48 deletions

View File

@ -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

View File

@ -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

View File

@ -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()