diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index f1dbe9443..00152cc17 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -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 diff --git a/docs/tutorial/7-schemas-and-client-libraries.md b/docs/tutorial/7-schemas-and-client-libraries.md index 7a2ad61c3..78a73e717 100644 --- a/docs/tutorial/7-schemas-and-client-libraries.md +++ b/docs/tutorial/7-schemas-and-client-libraries.md @@ -90,45 +90,127 @@ First we'll load the API schema using the command line client. $ coreapi get 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. -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 + + + + + Example + ... ## 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 `` and `` below with your +actual username and password. $ coreapi credentials add 127.0.0.1 : --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 [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 diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 81f8f8c83..ae5f22606 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -23,8 +23,9 @@ from django.conf.urls import url 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 @@ -262,7 +263,7 @@ class SimpleRouter(BaseRouter): return ret - def get_links(self): + def get_links(self, request=None): content = {} for prefix, viewset, basename in self.registry: @@ -284,13 +285,23 @@ class SimpleRouter(BaseRouter): 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): @@ -298,7 +309,7 @@ class SimpleRouter(BaseRouter): 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: @@ -336,9 +347,8 @@ class DefaultRouter(SimpleRouter): 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] + router = self class APIRoot(views.APIView): _ignore_model_permissions = True @@ -346,6 +356,10 @@ class DefaultRouter(SimpleRouter): def get(self, request, *args, **kwargs): 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) ret = OrderedDict()