Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
bc836aa
Initial pass at schema support
lovelydinosaur Jun 8, 2016
b64340b
Add coreapi to optional requirements.
lovelydinosaur Jun 9, 2016
c890ad4
Clean up test failures
lovelydinosaur Jun 9, 2016
2d28390
Add missing newline
lovelydinosaur Jun 9, 2016
744dba4
Minor docs update
lovelydinosaur Jun 9, 2016
56ece73
Version bump for coreapi in requirements
lovelydinosaur Jun 9, 2016
99adbf1
Catch SyntaxError when importing coreapi with python 3.2
lovelydinosaur Jun 9, 2016
80c595e
Add --diff to isort
lovelydinosaur Jun 9, 2016
47c7765
Import coreapi from compat
lovelydinosaur Jun 9, 2016
29e228d
Fail gracefully if attempting to use schemas without coreapi being in…
lovelydinosaur Jun 9, 2016
eeffca4
Tutorial updates
lovelydinosaur Jun 9, 2016
6c60f58
Docs update
lovelydinosaur Jun 9, 2016
b7fcdd2
Initial schema generation & first tutorial 7 draft
lovelydinosaur Jun 10, 2016
2e60f41
Spelling
lovelydinosaur Jun 10, 2016
2ffa145
Remove unused variable
lovelydinosaur Jun 10, 2016
b709dd4
Docs tweak
lovelydinosaur Jun 10, 2016
474a23e
Merge branch 'master' into schema-support
lovelydinosaur Jun 14, 2016
4822896
Added SchemaGenerator class
lovelydinosaur Jun 15, 2016
1f76cca
Fail gracefully if coreapi is not installed and SchemaGenerator is used
lovelydinosaur Jun 15, 2016
cad24b1
Schema docs, pagination controls, filter controls
lovelydinosaur Jun 21, 2016
8fb2602
Resolve NameError
lovelydinosaur Jun 21, 2016
b438281
Add 'view' argument to 'get_fields()'
lovelydinosaur Jun 21, 2016
8519b4e
Remove extranous blank line
lovelydinosaur Jun 21, 2016
2f5c974
Add integration tests for schema generation
lovelydinosaur Jun 22, 2016
e78753d
Only set 'encoding' if a 'form' or 'body' field exists
lovelydinosaur Jun 22, 2016
84bb5ea
Do not use schmea in tests if coreapi is not installed
lovelydinosaur Jun 24, 2016
63e8467
Inital pass at API client docs
lovelydinosaur Jun 29, 2016
bdbcb33
Inital pass at API client docs
lovelydinosaur Jun 29, 2016
7236af3
More work towards client documentation
lovelydinosaur Jun 30, 2016
89540ab
Add coreapi to optional packages list
lovelydinosaur Jul 4, 2016
e3ced75
Clean up API clients docs
lovelydinosaur Jul 4, 2016
12be5b3
Resolve typo
lovelydinosaur Jul 4, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Initial schema generation & first tutorial 7 draft
  • Loading branch information
lovelydinosaur committed Jun 10, 2016
commit b7fcdd257e1fe240c90a62d4f2edb154dd4663b7
26 changes: 3 additions & 23 deletions docs/tutorial/6-viewsets-and-routers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

## 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.

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
[tut-7]: 7-schemas-and-client-libraries.md
118 changes: 100 additions & 18 deletions docs/tutorial/7-schemas-and-client-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,45 +90,127 @@ First we'll load the API schema using the command line client.
$ coreapi get http://127.0.0.1:8000/
<Pastebin API "http://127.0.0.1:8000/">
snippets: {
create(code, [title], [linenos], [language], [style])
destroy(id)
highlight(id)
highlight(pk)
list()
partial_update(id, [title], [code], [linenos], [language], [style])
retrieve(id)
update(id, code, [title], [linenos], [language], [style])
retrieve(pk)
}
users: {
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.

Let's try listing the existing snippets, using the command line client:

$ 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.

We can now interact with the API using the command line client:
$ coreapi action snippets highlight --param pk 1
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

$ coreapi action list_snippets
<html>
<head>
<title>Example</title>
...

## 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
Added credentials
127.0.0.1 "Basic <...>"

Now if we fetch the schema again, we should be able to see the full
set of available interactions.

$ 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)
}

We're now able to interact with these endpoints. For example, to create a new
snippet:

$ 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"
}

And to delete a snippet:

$ 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.

## Using a client library
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.

*TODO - document using python client library, rather than the command line tool.*
You can review the final [tutorial code][repo] on GitHub, or try out a live example in [the sandbox][sandbox].

## Using another schema format
## Onwards and upwards

*TODO - document using OpenAPI instead.*
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:

## Customizing schema generation
* 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.

*TODO - document writing an explict schema view.*
**Now go build awesome things.**

[coreapi]: http://www.coreapi.org
[corejson]: http://www.coreapi.org/specification/encoding/#core-json-encoding
[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
28 changes: 21 additions & 7 deletions rest_framework/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
from django.core.exceptions import ImproperlyConfigured
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.request import override_method
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.settings import api_settings
Expand Down Expand Up @@ -262,7 +263,7 @@ def get_urls(self):

return ret

def get_links(self):
def get_links(self, request=None):
content = {}

for prefix, viewset, basename in self.registry:
Expand All @@ -284,21 +285,31 @@ def get_links(self):
continue

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:
content[prefix] = {}
link = self.get_link(viewset, url, method)
content[prefix][action] = link
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 = []

for variable in uritemplate.variables(url):
field = coreapi.Field(name=variable, location='path', required=True)
fields.append(field)

if method in ('put', 'patch', 'post'):
cls = viewset().get_serializer_class()
cls = view_instance.get_serializer_class()
serializer = cls()
for field in serializer.fields.values():
if field.read_only:
Expand Down Expand Up @@ -336,16 +347,19 @@ def get_api_root_view(self):

if self.schema_title:
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]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make more sense to expect users to include the CoreJSONRenderer in their default renderers, and raise an error if it is missing from the defaults?

router = self

class APIRoot(views.APIView):
_ignore_model_permissions = True
renderer_classes = view_renderers

def get(self, request, *args, **kwargs):
if request.accepted_renderer.format == 'corejson':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason why this couldn't be brought into the CoreJSONRenderer?

Copy link
Contributor Author

@lovelydinosaur lovelydinosaur Jun 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is less what to do with CoreJSONRenderer and more about making sure that we preserve the behavior of that view for anything that isn't a schema renderer.

(Also I'd consider that part still slightly in flux - clearly needs refinement)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

making sure that we preserve the behavior of that view for anything that isn't a schema renderer

Right, my main concern with that line (which I'm sure will probably change another few times before this is merged) is that it's hard-coding corejson as the only renderer with schema support.

Also I'd consider that part still slightly in flux - clearly needs refinement

Definitely understandable.

content = router.get_links(request)
if not content:
raise exceptions.PermissionDenied()
schema = coreapi.Document(title=router.schema_title, content=content)
return Response(schema)

ret = OrderedDict()
Expand Down