diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md deleted file mode 100644 index 9fa1ba2e3..000000000 --- a/docs/api-guide/schemas.md +++ /dev/null @@ -1,383 +0,0 @@ -source: schemas.py - -# Schemas - -> A machine-readable [schema] describes what resources are available via the API, what their URLs are, how they are represented and what operations they support. -> -> — Heroku, [JSON Schema for the Heroku Platform API][cite] - -API schemas are a useful tool that allow for a range of use cases, including -generating reference documentation, or driving dynamic client libraries that -can interact with your API. - -## Representing schemas internally - -REST framework uses [Core API][coreapi] in order to model schema information in -a format-independent representation. This information can then be rendered -into various different schema formats, or used to generate API documentation. - -When using Core API, a schema is represented as a `Document` which is the -top-level container object for information about the API. Available API -interactions are represented using `Link` objects. Each link includes a URL, -HTTP method, and may include a list of `Field` instances, which describe any -parameters that may be accepted by the API endpoint. The `Link` and `Field` -instances may also include descriptions, that allow an API schema to be -rendered into user documentation. - -Here's an example of an API description that includes a single `search` -endpoint: - - coreapi.Document( - title='Flight Search API', - url='https://api.example.org/', - content={ - 'search': coreapi.Link( - url='/search/', - action='get', - fields=[ - coreapi.Field( - name='from', - required=True, - location='query', - description='City name or airport code.' - ), - coreapi.Field( - name='to', - required=True, - location='query', - description='City name or airport code.' - ), - coreapi.Field( - name='date', - required=True, - location='query', - description='Flight date in "YYYY-MM-DD" format.' - ) - ], - description='Return flight availability and prices.' - ) - } - ) - -## Schema output formats - -In order to be presented in an HTTP response, the internal representation -has to be rendered into the actual bytes that are used in the response. - -[Core JSON][corejson] is designed as a canonical format for use with Core API. -REST framework includes a renderer class for handling this media type, which -is available as `renderers.CoreJSONRenderer`. - -Other schema formats such as [Open API][open-api] (Formerly "Swagger"), -[JSON HyperSchema][json-hyperschema], or [API Blueprint][api-blueprint] can -also be supported by implementing a custom renderer class. - -## Schemas vs Hypermedia - -It's worth pointing out here that Core API can also be used to model hypermedia -responses, which present an alternative interaction style to API schemas. - -With an API schema, the entire available interface is presented up-front -as a single endpoint. Responses to individual API endpoints are then typically -presented as plain data, without any further interactions contained in each -response. - -With Hypermedia, the client is instead presented with a document containing -both data and available interactions. Each interaction results in a new -document, detailing both the current state and the available interactions. - -Further information and support on building Hypermedia APIs with REST framework -is planned for a future version. - ---- - -# Adding a schema - -You'll need to install the `coreapi` package in order to add schema support -for REST framework. - - pip install coreapi - -REST framework includes functionality for auto-generating a schema, -or allows you to specify one explicitly. There are a few different ways to -add a schema to your API, depending on exactly what you need. - -## Using DefaultRouter - -If you're using `DefaultRouter` then you can include an auto-generated schema, -simply by adding a `schema_title` argument to the router. - - router = DefaultRouter(schema_title='Server Monitoring API') - -The schema will be included at the root URL, `/`, and presented to clients -that include the Core JSON media type in their `Accept` header. - - $ http http://127.0.0.1:8000/ Accept:application/vnd.coreapi+json - HTTP/1.0 200 OK - Allow: GET, HEAD, OPTIONS - Content-Type: application/vnd.coreapi+json - - { - "_meta": { - "title": "Server Monitoring API" - }, - "_type": "document", - ... - } - -This is a great zero-configuration option for when you want to get up and -running really quickly. If you want a little more flexibility over the -schema output then you'll need to consider using `SchemaGenerator` instead. - -## Using SchemaGenerator - -The most common way to add a schema to your API is to use the `SchemaGenerator` -class to auto-generate the `Document` instance, and to return that from a view. - -This option gives you the flexibility of setting up the schema endpoint -with whatever behaviour you want. For example, you can apply different -permission, throttling or authentication policies to the schema endpoint. - -Here's an example of using `SchemaGenerator` together with a view to -return the schema. - -**views.py:** - - from rest_framework.decorators import api_view, renderer_classes - from rest_framework import renderers, schemas - - generator = schemas.SchemaGenerator(title='Bookings API') - - @api_view() - @renderer_classes([renderers.CoreJSONRenderer]) - def schema_view(request): - return generator.get_schema() - -**urls.py:** - - urlpatterns = [ - url('/', schema_view), - ... - ] - -You can also serve different schemas to different users, depending on the -permissions they have available. This approach can be used to ensure that -unauthenticated requests are presented with a different schema to -authenticated requests, or to ensure that different parts of the API are -made visible to different users depending on their role. - -In order to present a schema with endpoints filtered by user permissions, -you need to pass the `request` argument to the `get_schema()` method, like so: - - @api_view() - @renderer_classes([renderers.CoreJSONRenderer]) - def schema_view(request): - return generator.get_schema(request=request) - -## Explicit schema definition - -An alternative to the auto-generated approach is to specify the API schema -explicitly, by declaring a `Document` object in your codebase. Doing so is a -little more work, but ensures that you have full control over the schema -representation. - - import coreapi - from rest_framework.decorators import api_view, renderer_classes - from rest_framework import renderers - - schema = coreapi.Document( - title='Bookings API', - content={ - ... - } - ) - - @api_view() - @renderer_classes([renderers.CoreJSONRenderer]) - def schema_view(request): - return schema - -## Static schema file - -A final option is to write your API schema as a static file, using one -of the available formats, such as Core JSON or Open API. - -You could then either: - -* Write a schema definition as a static file, and [serve the static file directly][static-files]. -* Write a schema definition that is loaded using `Core API`, and then - rendered to one of many available formats, depending on the client request. - ---- - -# API Reference - -## SchemaGenerator - -A class that deals with introspecting your API views, which can be used to -generate a schema. - -Typically you'll instantiate `SchemaGenerator` with a single argument, like so: - - generator = SchemaGenerator(title='Stock Prices API') - -Arguments: - -* `title` - The name of the API. **required** -* `patterns` - A list of URLs to inspect when generating the schema. Defaults to the project's URL conf. -* `urlconf` - A URL conf module name to use when generating the schema. Defaults to `settings.ROOT_URLCONF`. - -### get_schema() - -Returns a `coreapi.Document` instance that represents the API schema. - - @api_view - @renderer_classes([renderers.CoreJSONRenderer]) - def schema_view(request): - return generator.get_schema() - -Arguments: - -* `request` - The incoming request. Optionally used if you want to apply per-user permissions to the schema-generation. - ---- - -## Core API - -This documentation gives a brief overview of the components within the `coreapi` -package that are used to represent an API schema. - -Note that these classes are imported from the `coreapi` package, rather than -from the `rest_framework` package. - -### Document - -Represents a container for the API schema. - -#### `title` - -A name for the API. - -#### `url` - -A canonical URL for the API. - -#### `content` - -A dictionary, containing the `Link` objects that the schema contains. - -In order to provide more structure to the schema, the `content` dictionary -may be nested, typically to a second level. For example: - - content={ - "bookings": { - "list": Link(...), - "create": Link(...), - ... - }, - "venues": { - "list": Link(...), - ... - }, - ... - } - -### Link - -Represents an individual API endpoint. - -#### `url` - -The URL of the endpoint. May be a URI template, such as `/users/{username}/`. - -#### `action` - -The HTTP method associated with the endpoint. Note that URLs that support -more than one HTTP method, should correspond to a single `Link` for each. - -#### `fields` - -A list of `Field` instances, describing the available parameters on the input. - -#### `description` - -A short description of the meaning and intended usage of the endpoint. - -### Field - -Represents a single input parameter on a given API endpoint. - -#### `name` - -A descriptive name for the input. - -#### `required` - -A boolean, indicated if the client is required to included a value, or if -the parameter can be omitted. - -#### `location` - -Determines how the information is encoded into the request. Should be one of -the following strings: - -**"path"** - -Included in a templated URI. For example a `url` value of `/products/{product_code}/` could be used together with a `"path"` field, to handle API inputs in a URL path such as `/products/slim-fit-jeans/`. - -These fields will normally correspond with [named arguments in the project URL conf][named-arguments]. - -**"query"** - -Included as a URL query parameter. For example `?search=sale`. Typically for `GET` requests. - -These fields will normally correspond with pagination and filtering controls on a view. - -**"form"** - -Included in the request body, as a single item of a JSON object or HTML form. For example `{"colour": "blue", ...}`. Typically for `POST`, `PUT` and `PATCH` requests. Multiple `"form"` fields may be included on a single link. - -These fields will normally correspond with serializer fields on a view. - -**"body"** - -Included as the complete request body. Typically for `POST`, `PUT` and `PATCH` requests. No more than one `"body"` field may exist on a link. May not be used together with `"form"` fields. - -These fields will normally correspond with views that use `ListSerializer` to validate the request input, or with file upload views. - -#### `encoding` - -**"application/json"** - -JSON encoded request content. Corresponds to views using `JSONParser`. -Valid only if either one or more `location="form"` fields, or a single -`location="body"` field is included on the `Link`. - -**"multipart/form-data"** - -Multipart encoded request content. Corresponds to views using `MultiPartParser`. -Valid only if one or more `location="form"` fields is included on the `Link`. - -**"application/x-www-form-urlencoded"** - -URL encoded request content. Corresponds to views using `FormParser`. Valid -only if one or more `location="form"` fields is included on the `Link`. - -**"application/octet-stream"** - -Binary upload request content. Corresponds to views using `FileUploadParser`. -Valid only if a `location="body"` field is included on the `Link`. - -#### `description` - -A short description of the meaning and intended usage of the input field. - - -[cite]: https://blog.heroku.com/archives/2014/1/8/json_schema_for_heroku_platform_api -[coreapi]: http://www.coreapi.org/ -[corejson]: http://www.coreapi.org/specification/encoding/#core-json-encoding -[open-api]: https://openapis.org/ -[json-hyperschema]: http://json-schema.org/latest/json-schema-hypermedia.html -[api-blueprint]: https://apiblueprint.org/ -[static-files]: https://docs.djangoproject.com/en/dev/howto/static-files/ -[named-arguments]: https://docs.djangoproject.com/en/dev/topics/http/urls/#named-groups diff --git a/docs/img/corejson-format.png b/docs/img/corejson-format.png deleted file mode 100644 index 36c197a0d..000000000 Binary files a/docs/img/corejson-format.png and /dev/null differ diff --git a/docs/index.md b/docs/index.md index 072d80c66..cc8d183f4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -91,7 +91,6 @@ REST framework requires the following: The following packages are optional: -* [coreapi][coreapi] (1.21.0+) - Schema generation support. * [Markdown][markdown] (2.1.0+) - Markdown support for the browsable API. * [django-filter][django-filter] (0.9.2+) - Filtering support. * [django-crispy-forms][django-crispy-forms] - Improved HTML display for filtering. @@ -215,7 +214,6 @@ The API guide is your complete reference manual to all the functionality provide * [Versioning][versioning] * [Content negotiation][contentnegotiation] * [Metadata][metadata] -* [Schemas][schemas] * [Format suffixes][formatsuffixes] * [Returning URLs][reverse] * [Exceptions][exceptions] @@ -228,7 +226,6 @@ The API guide is your complete reference manual to all the functionality provide General guides to using REST framework. * [Documenting your API][documenting-your-api] -* [API Clients][api-clients] * [Internationalization][internationalization] * [AJAX, CSRF & CORS][ajax-csrf-cors] * [HTML & Forms][html-and-forms] @@ -299,7 +296,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [redhat]: https://www.redhat.com/ [heroku]: https://www.heroku.com/ [eventbrite]: https://www.eventbrite.co.uk/about/ -[coreapi]: http://pypi.python.org/pypi/coreapi/ [markdown]: http://pypi.python.org/pypi/Markdown/ [django-filter]: http://pypi.python.org/pypi/django-filter [django-crispy-forms]: https://github.com/maraujop/django-crispy-forms @@ -322,7 +318,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [tut-4]: tutorial/4-authentication-and-permissions.md [tut-5]: tutorial/5-relationships-and-hyperlinked-apis.md [tut-6]: tutorial/6-viewsets-and-routers.md -[tut-7]: tutorial/7-schemas-and-client-libraries.md [request]: api-guide/requests.md [response]: api-guide/responses.md @@ -344,7 +339,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [versioning]: api-guide/versioning.md [contentnegotiation]: api-guide/content-negotiation.md [metadata]: api-guide/metadata.md -[schemas]: 'api-guide/schemas.md' [formatsuffixes]: api-guide/format-suffixes.md [reverse]: api-guide/reverse.md [exceptions]: api-guide/exceptions.md @@ -353,7 +347,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [settings]: api-guide/settings.md [documenting-your-api]: topics/documenting-your-api.md -[api-clients]: topics/api-clients.md [internationalization]: topics/internationalization.md [ajax-csrf-cors]: topics/ajax-csrf-cors.md [html-and-forms]: topics/html-and-forms.md diff --git a/docs/topics/api-clients.md b/docs/topics/api-clients.md deleted file mode 100644 index 5f09c2a8f..000000000 --- a/docs/topics/api-clients.md +++ /dev/null @@ -1,294 +0,0 @@ -# API Clients - -An API client handles the underlying details of how network requests are made -and how responses are decoded. They present the developer with an application -interface to work against, rather than working directly with the network interface. - -The API clients documented here are not restricted to APIs built with Django REST framework. - They can be used with any API that exposes a supported schema format. - -For example, [the Heroku platform API][heroku-api] exposes a schema in the JSON -Hyperschema format. As a result, the Core API command line client and Python -client library can be [used to interact with the Heroku API][heroku-example]. - -## Client-side Core API - -[Core API][core-api] is a document specification that can be used to describe APIs. It can -be used either server-side, as is done with REST framework's [schema generation][schema-generation], -or used client-side, as described here. - -When used client-side, Core API allows for *dynamically driven client libraries* -that can interact with any API that exposes a supported schema or hypermedia -format. - -Using a dynamically driven client has a number of advantages over interacting -with an API by building HTTP requests directly. - -#### More meaningful interaction - -API interactions are presented in a more meaningful way. You're working at -the application interface layer, rather than the network interface layer. - -#### Resilience & evolvability - -The client determines what endpoints are available, what parameters exist -against each particular endpoint, and how HTTP requests are formed. - -This also allows for a degree of API evolvability. URLs can be modified -without breaking existing clients, or more efficient encodings can be used -on-the-wire, with clients transparently upgrading. - -#### Self-descriptive APIs - -A dynamically driven client is able to present documentation on the API to the -end user. This documentation allows the user to discover the available endpoints -and parameters, and better understand the API they are working with. - -Because this documentation is driven by the API schema it will always be fully -up to date with the most recently deployed version of the service. - ---- - -# Command line client - -The command line client allows you to inspect and interact with any API that -exposes a supported schema format. - -## Getting started - -To install the Core API command line client, use `pip`. - - $ pip install coreapi - -To start inspecting and interacting with an API the schema must first be loaded -from the network. - - - $ coreapi get http://api.example.org/ - - snippets: { - create(code, [title], [linenos], [language], [style]) - destroy(pk) - highlight(pk) - list([page]) - partial_update(pk, [title], [code], [linenos], [language], [style]) - retrieve(pk) - update(pk, code, [title], [linenos], [language], [style]) - } - users: { - list([page]) - retrieve(pk) - } - -This will then load the schema, displaying the resulting `Document`. This -`Document` includes all the available interactions that may be made against the API. - -To interact with the API, use the `action` command. This command requires a list -of keys that are used to index into the link. - - $ coreapi action users list - [ - { - "url": "http://127.0.0.1:8000/users/2/", - "id": 2, - "username": "aziz", - "snippets": [] - }, - ... - ] - -To inspect the underlying HTTP request and response, use the `--debug` flag. - - $ coreapi action users list --debug - > GET /users/ HTTP/1.1 - > Accept: application/vnd.coreapi+json, */* - > Authorization: Basic bWF4Om1heA== - > Host: 127.0.0.1 - > User-Agent: coreapi - < 200 OK - < Allow: GET, HEAD, OPTIONS - < Content-Type: application/json - < Date: Thu, 30 Jun 2016 10:51:46 GMT - < Server: WSGIServer/0.1 Python/2.7.10 - < Vary: Accept, Cookie - < - < [{"url":"http://127.0.0.1/users/2/","id":2,"username":"aziz","snippets":[]},{"url":"http://127.0.0.1/users/3/","id":3,"username":"amy","snippets":["http://127.0.0.1/snippets/3/"]},{"url":"http://127.0.0.1/users/4/","id":4,"username":"max","snippets":["http://127.0.0.1/snippets/4/","http://127.0.0.1/snippets/5/","http://127.0.0.1/snippets/6/","http://127.0.0.1/snippets/7/"]},{"url":"http://127.0.0.1/users/5/","id":5,"username":"jose","snippets":[]},{"url":"http://127.0.0.1/users/6/","id":6,"username":"admin","snippets":["http://127.0.0.1/snippets/1/","http://127.0.0.1/snippets/2/"]}] - - [ - ... - ] - -Some actions may include optional or required parameters. - - $ coreapi action users create --params username example - -## Authentication & headers - -The `credentials` command is used to manage the request `Authentication:` header. -Any credentials added are always linked to a particular domain, so as to ensure -that credentials are not leaked across differing APIs. - -The format for adding a new credential is: - - coreapi credentials add - -For instance: - - coreapi credentials add api.example.org "Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b" - -The optional `--auth` flag also allows you to add specific types of authentication, -handling the encoding for you. Currently only `"basic"` is supported as an option here. -For example: - - coreapi credentials add api.example.org tomchristie:foobar --auth basic - -You can also add specific request headers, using the `headers` command: - - coreapi headers add api.example.org x-api-version 2 - -For more information and a listing of the available subcommands use `coreapi -credentials --help` or `coreapi headers --help`. - -## Utilities - -The command line client includes functionality for bookmarking API URLs -under a memorable name. For example, you can add a bookmark for the -existing API, like so... - - coreapi bookmarks add accountmanagement - -There is also functionality for navigating forward or backward through the -history of which API URLs have been accessed. - - coreapi history show - coreapi history back - -For more information and a listing of the available subcommands use -`coreapi bookmarks --help` or `coreapi history --help`. - -## Other commands - -To display the current `Document`: - - coreapi show - -To reload the current `Document` from the network: - - coreapi reload - -To load a schema file from disk: - - coreapi load my-api-schema.json --format corejson - -To remove the current document, along with all currently saved history, -credentials, headers and bookmarks: - - coreapi clear - ---- - -# Python client library - -The `coreapi` Python package allows you to programatically interact with any -API that exposes a supported schema format. - -## Getting started - -You'll need to install the `coreapi` package using `pip` before you can get -started. Once you've done so, open up a python terminal. - -In order to start working with an API, we first need a `Client` instance. The -client holds any configuration around which codecs and transports are supported -when interacting with an API, which allows you to provide for more advanced -kinds of behaviour. - - import coreapi - client = coreapi.Client() - -Once we have a `Client` instance, we can fetch an API schema from the network. - - schema = client.get('https://api.example.org/') - -The object returned from this call will be a `Document` instance, which is -the internal representation of the interface that we are interacting with. - -Now that we have our schema `Document`, we can now start to interact with the API: - - users = client.action(schema, ['users', 'list']) - -Some endpoints may include named parameters, which might be either optional or required: - - new_user = client.action(schema, ['users', 'create'], params={"username": "max"}) - -## Codecs - -Codecs are responsible for encoding or decoding Documents. - -The decoding process is used by a client to take a bytestring of an API schema -definition, and returning the Core API `Document` that represents that interface. - -A codec should be associated with a particular media type, such as **TODO**. - -This media type is used by the server in the response `Content-Type` header, -in order to indicate what kind of data is being returned in the response. - -#### Configuring codecs - -The codecs that are available can be configured when instantiating a client. -The keyword argument used here is `decoders`, because in the context of a -client the codecs are only for *decoding* responses. - -In the following example we'll configure a client to only accept `Core JSON` -and `JSON` responses. This will allow us to receive and decode a Core JSON schema, -and subsequently to receive JSON responses made against the API. - - from coreapi import codecs, Client - - decoders = [codecs.CoreJSONCodec(), codecs.JSONCodec()] - client = Client(decoders=decoders) - -#### Loading and saving schemas - -You can use a codec directly, in order to load an existing schema definition, -and return the resulting `Document`. - - schema_definition = open('my-api-schema.json', 'r').read() - codec = codecs.CoreJSONCodec() - schema = codec.load(schema_definition) - -You can also use a codec directly to generate a schema definition given a `Document` instance: - - schema_definition = codec.dump(schema) - output_file = open('my-api-schema.json', 'r') - output_file.write(schema_definition) - -## Transports - -Transports are responsible for making network requests. The set of transports -that a client has installed determines which network protocols it is able to -support. - -Currently the `coreapi` library only includes an HTTP/HTTPS transport, but -other protocols can also be supported. - -#### Configuring transports - -The behaviour of the network layer can be customized by configuring the -transports that the client is instantiated with. - - import requests - from coreapi import transports, Client - - credentials = {'api.example.org': 'Token 3bd44a009d16ff'} - transports = transports.HTTPTransport(credentials=credentials) - client = Client(transports=transports) - -More complex customizations can also be achieved, for example modifying the -underlying `requests.Session` instance to [attach transport adaptors][transport-adaptors] -that modify the outgoing requests. - -[heroku-api]: https://devcenter.heroku.com/categories/platform-api -[heroku-example]: http://www.coreapi.org/tools-and-resources/example-services/#heroku-json-hyper-schema -[core-api]: http://www.coreapi.org/ -[schema-generation]: ../api-guide/schemas.md -[transport-adaptors]: http://docs.python-requests.org/en/master/user/advanced/#transport-adapters diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index 00152cc17..f1dbe9443 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -130,7 +130,27 @@ 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. -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. +## Reviewing our work -[tut-7]: 7-schemas-and-client-libraries.md +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 diff --git a/docs/tutorial/7-schemas-and-client-libraries.md b/docs/tutorial/7-schemas-and-client-libraries.md deleted file mode 100644 index 8d772a5bf..000000000 --- a/docs/tutorial/7-schemas-and-client-libraries.md +++ /dev/null @@ -1,216 +0,0 @@ -# Tutorial 7: Schemas & client libraries - -A schema is a machine-readable document that describes the available API -endpoints, their URLS, and what operations they support. - -Schemas can be a useful tool for auto-generated documentation, and can also -be used to drive dynamic client libraries that can interact with the API. - -## Core API - -In order to provide schema support REST framework uses [Core API][coreapi]. - -Core API is a document specification for describing APIs. It is used to provide -an internal representation format of the available endpoints and possible -interactions that an API exposes. It can either be used server-side, or -client-side. - -When used server-side, Core API allows an API to support rendering to a wide -range of schema or hypermedia formats. - -When used client-side, Core API allows for dynamically driven client libraries -that can interact with any API that exposes a supported schema or hypermedia -format. - -## Adding a schema - -REST framework supports either explicitly defined schema views, or -automatically generated schemas. Since we're using viewsets and routers, -we can simply use the automatic schema generation. - -You'll need to install the `coreapi` python package in order to include an -API schema. - - $ pip install coreapi - -We can now include a schema for our API, by adding a `schema_title` argument to -the router instantiation. - - router = DefaultRouter(schema_title='Pastebin API') - -If you visit the API root endpoint in a browser you should now see `corejson` -representation become available as an option. - -![Schema format](../img/corejson-format.png) - -We can also request the schema from the command line, by specifying the desired -content type in the `Accept` header. - - $ http http://127.0.0.1:8000/ Accept:application/vnd.coreapi+json - HTTP/1.0 200 OK - Allow: GET, HEAD, OPTIONS - Content-Type: application/vnd.coreapi+json - - { - "_meta": { - "title": "Pastebin API" - }, - "_type": "document", - ... - -The default output style is to use the [Core JSON][corejson] encoding. - -Other schema formats, such as [Open API][openapi] (formerly Swagger) are -also supported. - -## Using a command line client - -Now that our API is exposing a schema endpoint, we can use a dynamic client -library to interact with the API. To demonstrate this, let's use the -Core API command line client. We've already installed the `coreapi` package -using `pip`, so the client tool should already be installed. Check that it -is available on the command line... - - $ coreapi - Usage: coreapi [OPTIONS] COMMAND [ARGS]... - - Command line client for interacting with CoreAPI services. - - Visit http://www.coreapi.org for more information. - - Options: - --version Display the package version number. - --help Show this message and exit. - - Commands: - ... - -First we'll load the API schema using the command line client. - - $ coreapi get http://127.0.0.1:8000/ - - snippets: { - highlight(pk) - list() - retrieve(pk) - } - users: { - list() - retrieve(pk) - } - -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 highlight HTML for a particular snippet we need to provide an id. - - $ coreapi action snippets highlight --param pk 1 - - - - - Example - ... - -## Authenticating our client - -If we want to be able to create, edit and delete snippets, we'll need to -authenticate as a valid user. In this case we'll just use basic auth. - -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 <...>" - -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. - -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/mkdocs.yml b/mkdocs.yml index b10fbefb5..19d1b3553 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -20,7 +20,6 @@ pages: - '4 - Authentication and permissions': 'tutorial/4-authentication-and-permissions.md' - '5 - Relationships and hyperlinked APIs': 'tutorial/5-relationships-and-hyperlinked-apis.md' - '6 - Viewsets and routers': 'tutorial/6-viewsets-and-routers.md' - - '7 - Schemas and client libraries': 'tutorial/7-schemas-and-client-libraries.md' - API Guide: - 'Requests': 'api-guide/requests.md' - 'Responses': 'api-guide/responses.md' @@ -42,7 +41,6 @@ pages: - 'Versioning': 'api-guide/versioning.md' - 'Content negotiation': 'api-guide/content-negotiation.md' - 'Metadata': 'api-guide/metadata.md' - - 'Schemas': 'api-guide/schemas.md' - 'Format suffixes': 'api-guide/format-suffixes.md' - 'Returning URLs': 'api-guide/reverse.md' - 'Exceptions': 'api-guide/exceptions.md' @@ -51,7 +49,6 @@ pages: - 'Settings': 'api-guide/settings.md' - Topics: - 'Documenting your API': 'topics/documenting-your-api.md' - - 'API Clients': 'topics/api-clients.md' - 'Internationalization': 'topics/internationalization.md' - 'AJAX, CSRF & CORS': 'topics/ajax-csrf-cors.md' - 'HTML & Forms': 'topics/html-and-forms.md' diff --git a/requirements/requirements-optionals.txt b/requirements/requirements-optionals.txt index 54c080491..241e1951d 100644 --- a/requirements/requirements-optionals.txt +++ b/requirements/requirements-optionals.txt @@ -2,4 +2,3 @@ markdown==2.6.4 django-guardian==1.4.3 django-filter==0.13.0 -coreapi==1.21.1 diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 9c69eaa03..dd30636f4 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -156,16 +156,6 @@ except ImportError: crispy_forms = None -# coreapi is optional (Note that uritemplate is a dependancy of coreapi) -try: - import coreapi - import uritemplate -except (ImportError, SyntaxError): - # SyntaxError is possible under python 3.2 - coreapi = None - uritemplate = None - - # Django-guardian is optional. Import only if guardian is in INSTALLED_APPS # Fixes (#1712). We keep the try/except for the test suite. guardian = None diff --git a/rest_framework/filters.py b/rest_framework/filters.py index caff1c17f..fdd9519c6 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -72,9 +72,6 @@ class BaseFilterBackend(object): """ raise NotImplementedError(".filter_queryset() must be overridden.") - def get_fields(self, view): - return [] - class DjangoFilterBackend(BaseFilterBackend): """ @@ -131,17 +128,6 @@ class DjangoFilterBackend(BaseFilterBackend): template = loader.get_template(self.template) return template_render(template, context) - def get_fields(self, view): - filter_class = getattr(view, 'filter_class', None) - if filter_class: - return list(filter_class().filters.keys()) - - filter_fields = getattr(view, 'filter_fields', None) - if filter_fields: - return filter_fields - - return [] - class SearchFilter(BaseFilterBackend): # The URL query parameter used for the search. @@ -231,9 +217,6 @@ class SearchFilter(BaseFilterBackend): template = loader.get_template(self.template) return template_render(template, context) - def get_fields(self, view): - return [self.search_param] - class OrderingFilter(BaseFilterBackend): # The URL query parameter used for the ordering. @@ -347,9 +330,6 @@ class OrderingFilter(BaseFilterBackend): context = self.get_template_context(request, queryset, view) return template_render(template, context) - def get_fields(self, view): - return [self.ordering_param] - class DjangoObjectPermissionsFilter(BaseFilterBackend): """ diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 6ad10d860..a66c7505c 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -157,9 +157,6 @@ class BasePagination(object): def get_results(self, data): return data['results'] - def get_fields(self, view): - return [] - class PageNumberPagination(BasePagination): """ @@ -283,11 +280,6 @@ class PageNumberPagination(BasePagination): context = self.get_html_context() return template_render(template, context) - def get_fields(self, view): - if self.page_size_query_param is None: - return [self.page_query_param] - return [self.page_query_param, self.page_size_query_param] - class LimitOffsetPagination(BasePagination): """ @@ -412,9 +404,6 @@ class LimitOffsetPagination(BasePagination): context = self.get_html_context() return template_render(template, context) - def get_fields(self, view): - return [self.limit_query_param, self.offset_query_param] - class CursorPagination(BasePagination): """ @@ -717,6 +706,3 @@ class CursorPagination(BasePagination): template = loader.get_template(self.template) context = self.get_html_context() return template_render(template, context) - - def get_fields(self, view): - return [self.cursor_query_param] diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index e313998d1..7ca680e74 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -22,8 +22,7 @@ from django.utils import six from rest_framework import VERSION, exceptions, serializers, status from rest_framework.compat import ( - INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi, - template_render + INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, template_render ) from rest_framework.exceptions import ParseError from rest_framework.request import is_form_media_type, override_method @@ -791,17 +790,3 @@ class MultiPartRenderer(BaseRenderer): "test case." % key ) return encode_multipart(self.BOUNDARY, data) - - -class CoreJSONRenderer(BaseRenderer): - media_type = 'application/vnd.coreapi+json' - charset = None - format = 'corejson' - - def __init__(self): - assert coreapi, 'Using CoreJSONRenderer, but `coreapi` is not installed.' - - def render(self, data, media_type=None, renderer_context=None): - indent = bool(renderer_context.get('indent', 0)) - codec = coreapi.codecs.CoreJSONCodec() - return codec.dump(data, indent=indent) diff --git a/rest_framework/routers.py b/rest_framework/routers.py index a71bb7791..70a1149ab 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -22,11 +22,9 @@ from django.conf.urls import url from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import NoReverseMatch -from rest_framework import exceptions, renderers, views +from rest_framework import views from rest_framework.response import Response from rest_framework.reverse import reverse -from rest_framework.schemas import SchemaGenerator -from rest_framework.settings import api_settings from rest_framework.urlpatterns import format_suffix_patterns Route = namedtuple('Route', ['url', 'mapping', 'name', 'initkwargs']) @@ -257,7 +255,6 @@ class SimpleRouter(BaseRouter): lookup=lookup, trailing_slash=self.trailing_slash ) - view = viewset.as_view(mapping, **route.initkwargs) name = route.name.format(basename=basename) ret.append(url(regex, view, name=name)) @@ -273,13 +270,8 @@ class DefaultRouter(SimpleRouter): include_root_view = True include_format_suffixes = True root_view_name = 'api-root' - schema_renderers = [renderers.CoreJSONRenderer] - def __init__(self, *args, **kwargs): - self.schema_title = kwargs.pop('schema_title', None) - super(DefaultRouter, self).__init__(*args, **kwargs) - - def get_api_root_view(self, schema_urls=None): + def get_api_root_view(self): """ Return a view to use as the API root. """ @@ -288,33 +280,10 @@ class DefaultRouter(SimpleRouter): for prefix, viewset, basename in self.registry: api_root_dict[prefix] = list_name.format(basename=basename) - view_renderers = list(api_settings.DEFAULT_RENDERER_CLASSES) - schema_media_types = [] - - if schema_urls and self.schema_title: - view_renderers += list(self.schema_renderers) - schema_generator = SchemaGenerator( - title=self.schema_title, - patterns=schema_urls - ) - schema_media_types = [ - renderer.media_type - for renderer in self.schema_renderers - ] - class APIRoot(views.APIView): _ignore_model_permissions = True - renderer_classes = view_renderers def get(self, request, *args, **kwargs): - if request.accepted_renderer.media_type in schema_media_types: - # Return a schema response. - schema = schema_generator.get_schema(request) - if schema is None: - raise exceptions.PermissionDenied() - return Response(schema) - - # Return a plain {"name": "hyperlink"} response. ret = OrderedDict() namespace = request.resolver_match.namespace for key, url_name in api_root_dict.items(): @@ -341,13 +310,15 @@ class DefaultRouter(SimpleRouter): Generate the list of URL patterns, including a default root view for the API, and appending `.json` style format suffixes. """ - urls = super(DefaultRouter, self).get_urls() + urls = [] if self.include_root_view: - view = self.get_api_root_view(schema_urls=urls) - root_url = url(r'^$', view, name=self.root_view_name) + root_url = url(r'^$', self.get_api_root_view(), name=self.root_view_name) urls.append(root_url) + default_urls = super(DefaultRouter, self).get_urls() + urls.extend(default_urls) + if self.include_format_suffixes: urls = format_suffix_patterns(urls) diff --git a/rest_framework/schemas.py b/rest_framework/schemas.py deleted file mode 100644 index cf84aca74..000000000 --- a/rest_framework/schemas.py +++ /dev/null @@ -1,300 +0,0 @@ -from importlib import import_module - -from django.conf import settings -from django.contrib.admindocs.views import simplify_regex -from django.core.urlresolvers import RegexURLPattern, RegexURLResolver -from django.utils import six - -from rest_framework import exceptions, serializers -from rest_framework.compat import coreapi, uritemplate -from rest_framework.request import clone_request -from rest_framework.views import APIView - - -def as_query_fields(items): - """ - Take a list of Fields and plain strings. - Convert any pain strings into `location='query'` Field instances. - """ - return [ - item if isinstance(item, coreapi.Field) else coreapi.Field(name=item, required=False, location='query') - for item in items - ] - - -def is_api_view(callback): - """ - Return `True` if the given view callback is a REST framework view/viewset. - """ - cls = getattr(callback, 'cls', None) - return (cls is not None) and issubclass(cls, APIView) - - -def insert_into(target, keys, item): - """ - Insert `item` into the nested dictionary `target`. - - For example: - - target = {} - insert_into(target, ('users', 'list'), Link(...)) - insert_into(target, ('users', 'detail'), Link(...)) - assert target == {'users': {'list': Link(...), 'detail': Link(...)}} - """ - for key in keys[:1]: - if key not in target: - target[key] = {} - target = target[key] - target[keys[-1]] = item - - -class SchemaGenerator(object): - default_mapping = { - 'get': 'read', - 'post': 'create', - 'put': 'update', - 'patch': 'partial_update', - 'delete': 'destroy', - } - - def __init__(self, title=None, patterns=None, urlconf=None): - assert coreapi, '`coreapi` must be installed for schema support.' - - if patterns is None and urlconf is not None: - if isinstance(urlconf, six.string_types): - urls = import_module(urlconf) - else: - urls = urlconf - patterns = urls.urlpatterns - elif patterns is None and urlconf is None: - urls = import_module(settings.ROOT_URLCONF) - patterns = urls.urlpatterns - - self.title = title - self.endpoints = self.get_api_endpoints(patterns) - - def get_schema(self, request=None): - if request is None: - endpoints = self.endpoints - else: - # Filter the list of endpoints to only include those that - # the user has permission on. - endpoints = [] - for key, link, callback in self.endpoints: - method = link.action.upper() - view = callback.cls() - view.request = clone_request(request, method) - try: - view.check_permissions(view.request) - except exceptions.APIException: - pass - else: - endpoints.append((key, link, callback)) - - if not endpoints: - return None - - # Generate the schema content structure, from the endpoints. - # ('users', 'list'), Link -> {'users': {'list': Link()}} - content = {} - for key, link, callback in endpoints: - insert_into(content, key, link) - - # Return the schema document. - return coreapi.Document(title=self.title, content=content) - - def get_api_endpoints(self, patterns, prefix=''): - """ - Return a list of all available API endpoints by inspecting the URL conf. - """ - api_endpoints = [] - - for pattern in patterns: - path_regex = prefix + pattern.regex.pattern - - if isinstance(pattern, RegexURLPattern): - path = self.get_path(path_regex) - callback = pattern.callback - if self.should_include_endpoint(path, callback): - for method in self.get_allowed_methods(callback): - key = self.get_key(path, method, callback) - link = self.get_link(path, method, callback) - endpoint = (key, link, callback) - api_endpoints.append(endpoint) - - elif isinstance(pattern, RegexURLResolver): - nested_endpoints = self.get_api_endpoints( - patterns=pattern.url_patterns, - prefix=path_regex - ) - api_endpoints.extend(nested_endpoints) - - return api_endpoints - - def get_path(self, path_regex): - """ - Given a URL conf regex, return a URI template string. - """ - path = simplify_regex(path_regex) - path = path.replace('<', '{').replace('>', '}') - return path - - def should_include_endpoint(self, path, callback): - """ - Return `True` if the given endpoint should be included. - """ - if not is_api_view(callback): - return False # Ignore anything except REST framework views. - - if path.endswith('.{format}') or path.endswith('.{format}/'): - return False # Ignore .json style URLs. - - if path == '/': - return False # Ignore the root endpoint. - - return True - - def get_allowed_methods(self, callback): - """ - Return a list of the valid HTTP methods for this endpoint. - """ - if hasattr(callback, 'actions'): - return [method.upper() for method in callback.actions.keys()] - - return [ - method for method in - callback.cls().allowed_methods if method != 'OPTIONS' - ] - - def get_key(self, path, method, callback): - """ - Return a tuple of strings, indicating the identity to use for a - given endpoint. eg. ('users', 'list'). - """ - category = None - for item in path.strip('/').split('/'): - if '{' in item: - break - category = item - - actions = getattr(callback, 'actions', self.default_mapping) - action = actions[method.lower()] - - if category: - return (category, action) - return (action,) - - # Methods for generating each individual `Link` instance... - - def get_link(self, path, method, callback): - """ - Return a `coreapi.Link` instance for the given endpoint. - """ - view = callback.cls() - - fields = self.get_path_fields(path, method, callback, view) - fields += self.get_serializer_fields(path, method, callback, view) - fields += self.get_pagination_fields(path, method, callback, view) - fields += self.get_filter_fields(path, method, callback, view) - - if fields and any([field.location in ('form', 'body') for field in fields]): - encoding = self.get_encoding(path, method, callback, view) - else: - encoding = None - - return coreapi.Link( - url=path, - action=method.lower(), - encoding=encoding, - fields=fields - ) - - def get_encoding(self, path, method, callback, view): - """ - Return the 'encoding' parameter to use for a given endpoint. - """ - # Core API supports the following request encodings over HTTP... - supported_media_types = set(( - 'application/json', - 'application/x-www-form-urlencoded', - 'multipart/form-data', - )) - parser_classes = getattr(view, 'parser_classes', []) - for parser_class in parser_classes: - media_type = getattr(parser_class, 'media_type', None) - if media_type in supported_media_types: - return media_type - # Raw binary uploads are supported with "application/octet-stream" - if media_type == '*/*': - return 'application/octet-stream' - - return None - - def get_path_fields(self, path, method, callback, view): - """ - Return a list of `coreapi.Field` instances corresponding to any - templated path variables. - """ - fields = [] - - for variable in uritemplate.variables(path): - field = coreapi.Field(name=variable, location='path', required=True) - fields.append(field) - - return fields - - def get_serializer_fields(self, path, method, callback, view): - """ - Return a list of `coreapi.Field` instances corresponding to any - request body input, as determined by the serializer class. - """ - if method not in ('PUT', 'PATCH', 'POST'): - return [] - - fields = [] - - serializer_class = view.get_serializer_class() - serializer = serializer_class() - - if isinstance(serializer, serializers.ListSerializer): - return coreapi.Field(name='data', location='body', required=True) - - if not isinstance(serializer, serializers.Serializer): - return [] - - for field in serializer.fields.values(): - if field.read_only: - continue - required = field.required and method != 'PATCH' - field = coreapi.Field(name=field.source, location='form', required=required) - fields.append(field) - - return fields - - def get_pagination_fields(self, path, method, callback, view): - if method != 'GET': - return [] - - if hasattr(callback, 'actions') and ('list' not in callback.actions.values()): - return [] - - if not hasattr(view, 'pagination_class'): - return [] - - paginator = view.pagination_class() - return as_query_fields(paginator.get_fields(view)) - - def get_filter_fields(self, path, method, callback, view): - if method != 'GET': - return [] - - if hasattr(callback, 'actions') and ('list' not in callback.actions.values()): - return [] - - if not hasattr(view, 'filter_backends'): - return [] - - fields = [] - for filter_backend in view.filter_backends: - fields += as_query_fields(filter_backend().get_fields(view)) - return fields diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index e5b52ea5f..f883b4925 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -13,7 +13,7 @@ from django.utils import six, timezone from django.utils.encoding import force_text from django.utils.functional import Promise -from rest_framework.compat import coreapi, total_seconds +from rest_framework.compat import total_seconds class JSONEncoder(json.JSONEncoder): @@ -64,9 +64,4 @@ class JSONEncoder(json.JSONEncoder): pass elif hasattr(obj, '__iter__'): return tuple(item for item in obj) - elif (coreapi is not None) and isinstance(obj, (coreapi.Document, coreapi.Error)): - raise RuntimeError( - 'Cannot return a coreapi object from a JSON view. ' - 'You should be using a schema renderer instead for this view.' - ) return super(JSONEncoder, self).default(obj) diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py index 7687448c4..05434b72e 100644 --- a/rest_framework/viewsets.py +++ b/rest_framework/viewsets.py @@ -98,7 +98,6 @@ class ViewSetMixin(object): # resolved URL. view.cls = cls view.suffix = initkwargs.get('suffix', None) - view.actions = actions return csrf_exempt(view) def initialize_request(self, request, *args, **kwargs): diff --git a/runtests.py b/runtests.py index e97ac0367..1627e33b2 100755 --- a/runtests.py +++ b/runtests.py @@ -14,7 +14,7 @@ PYTEST_ARGS = { FLAKE8_ARGS = ['rest_framework', 'tests', '--ignore=E501'] -ISORT_ARGS = ['--recursive', '--check-only', '-o' 'uritemplate', '-p', 'tests', 'rest_framework', 'tests'] +ISORT_ARGS = ['--recursive', '--check-only', '-p', 'tests', 'rest_framework', 'tests'] sys.path.append(os.path.dirname(__file__)) diff --git a/schema-support b/schema-support deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/test_routers.py b/tests/test_routers.py index f45039f80..acab660d8 100644 --- a/tests/test_routers.py +++ b/tests/test_routers.py @@ -257,7 +257,7 @@ class TestNameableRoot(TestCase): def test_router_has_custom_name(self): expected = 'nameable-root' - self.assertEqual(expected, self.urls[-1].name) + self.assertEqual(expected, self.urls[0].name) class TestActionKeywordArgs(TestCase): diff --git a/tests/test_schemas.py b/tests/test_schemas.py deleted file mode 100644 index 7d3308ed9..000000000 --- a/tests/test_schemas.py +++ /dev/null @@ -1,137 +0,0 @@ -import unittest - -from django.conf.urls import include, url -from django.test import TestCase, override_settings - -from rest_framework import filters, pagination, permissions, serializers -from rest_framework.compat import coreapi -from rest_framework.routers import DefaultRouter -from rest_framework.test import APIClient -from rest_framework.viewsets import ModelViewSet - - -class MockUser(object): - def is_authenticated(self): - return True - - -class ExamplePagination(pagination.PageNumberPagination): - page_size = 100 - - -class ExampleSerializer(serializers.Serializer): - a = serializers.CharField(required=True) - b = serializers.CharField(required=False) - - -class ExampleViewSet(ModelViewSet): - pagination_class = ExamplePagination - permission_classes = [permissions.IsAuthenticatedOrReadOnly] - filter_backends = [filters.OrderingFilter] - serializer_class = ExampleSerializer - - -router = DefaultRouter(schema_title='Example API' if coreapi else None) -router.register('example', ExampleViewSet, base_name='example') -urlpatterns = [ - url(r'^', include(router.urls)) -] - - -@unittest.skipUnless(coreapi, 'coreapi is not installed') -@override_settings(ROOT_URLCONF='tests.test_schemas') -class TestRouterGeneratedSchema(TestCase): - def test_anonymous_request(self): - client = APIClient() - response = client.get('/', HTTP_ACCEPT='application/vnd.coreapi+json') - self.assertEqual(response.status_code, 200) - expected = coreapi.Document( - url='', - title='Example API', - content={ - 'example': { - 'list': coreapi.Link( - url='/example/', - action='get', - fields=[ - coreapi.Field('page', required=False, location='query'), - coreapi.Field('ordering', required=False, location='query') - ] - ), - 'retrieve': coreapi.Link( - url='/example/{pk}/', - action='get', - fields=[ - coreapi.Field('pk', required=True, location='path') - ] - ) - } - } - ) - self.assertEqual(response.data, expected) - - def test_authenticated_request(self): - client = APIClient() - client.force_authenticate(MockUser()) - response = client.get('/', HTTP_ACCEPT='application/vnd.coreapi+json') - self.assertEqual(response.status_code, 200) - expected = coreapi.Document( - url='', - title='Example API', - content={ - 'example': { - 'list': coreapi.Link( - url='/example/', - action='get', - fields=[ - coreapi.Field('page', required=False, location='query'), - coreapi.Field('ordering', required=False, location='query') - ] - ), - 'create': coreapi.Link( - url='/example/', - action='post', - encoding='application/json', - fields=[ - coreapi.Field('a', required=True, location='form'), - coreapi.Field('b', required=False, location='form') - ] - ), - 'retrieve': coreapi.Link( - url='/example/{pk}/', - action='get', - fields=[ - coreapi.Field('pk', required=True, location='path') - ] - ), - 'update': coreapi.Link( - url='/example/{pk}/', - action='put', - encoding='application/json', - fields=[ - coreapi.Field('pk', required=True, location='path'), - coreapi.Field('a', required=True, location='form'), - coreapi.Field('b', required=False, location='form') - ] - ), - 'partial_update': coreapi.Link( - url='/example/{pk}/', - action='patch', - encoding='application/json', - fields=[ - coreapi.Field('pk', required=True, location='path'), - coreapi.Field('a', required=False, location='form'), - coreapi.Field('b', required=False, location='form') - ] - ), - 'destroy': coreapi.Link( - url='/example/{pk}/', - action='delete', - fields=[ - coreapi.Field('pk', required=True, location='path') - ] - ) - } - } - ) - self.assertEqual(response.data, expected)