diff options
Diffstat (limited to 'docs')
| -rwxr-xr-x | docs/api-guide/authentication.md | 149 | ||||
| -rw-r--r-- | docs/api-guide/exceptions.md | 20 | ||||
| -rw-r--r-- | docs/api-guide/pagination.md | 169 | ||||
| -rw-r--r-- | docs/api-guide/parsers.md | 83 | ||||
| -rw-r--r-- | docs/api-guide/permissions.md | 17 | ||||
| -rw-r--r-- | docs/api-guide/renderers.md | 164 | ||||
| -rw-r--r-- | docs/api-guide/serializers.md | 63 | ||||
| -rw-r--r-- | docs/api-guide/settings.md | 28 | ||||
| -rw-r--r-- | docs/api-guide/testing.md | 4 | ||||
| -rw-r--r-- | docs/api-guide/versioning.md | 205 | ||||
| -rw-r--r-- | docs/img/link-header-pagination.png | bin | 0 -> 35799 bytes | |||
| -rw-r--r-- | docs/index.md | 21 | ||||
| -rw-r--r-- | docs/topics/3.1-announcement.md | 7 | ||||
| -rw-r--r-- | docs/topics/internationalization.md | 72 | ||||
| -rw-r--r-- | docs/topics/project-management.md | 55 | ||||
| -rw-r--r-- | docs/tutorial/2-requests-and-responses.md | 2 | 
16 files changed, 699 insertions, 360 deletions
| diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 1222dbf0..ba114513 100755 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -126,7 +126,6 @@ To use the `TokenAuthentication` scheme you'll need to [configure the authentica          'rest_framework.authtoken'      ) -  ---  **Note:** Make sure to run `manage.py syncdb` after changing your settings. The `rest_framework.authtoken` app provides both Django (from v1.7) and South database migrations. See [Schema migrations](#schema-migrations) below. @@ -249,107 +248,6 @@ Unauthenticated responses that are denied permission will result in an `HTTP 403  If you're using an AJAX style API with SessionAuthentication, you'll need to make sure you include a valid CSRF token for any "unsafe" HTTP method calls, such as `PUT`, `PATCH`, `POST` or `DELETE` requests.  See the [Django CSRF documentation][csrf-ajax] for more details. -## OAuthAuthentication - -This authentication uses [OAuth 1.0a][oauth-1.0a] authentication scheme.  OAuth 1.0a provides signature validation which provides a reasonable level of security over plain non-HTTPS connections.  However, it may also be considered more complicated than OAuth2, as it requires clients to sign their requests. - -This authentication class depends on the optional `django-oauth-plus` and `oauth2` packages.  In order to make it work you must install these packages and add `oauth_provider` to your `INSTALLED_APPS`: - -    INSTALLED_APPS = ( -        ... -        `oauth_provider`, -    ) - -Don't forget to run `syncdb` once you've added the package. - -    python manage.py syncdb - -#### Getting started with django-oauth-plus - -The OAuthAuthentication class only provides token verification and signature validation for requests.  It doesn't provide authorization flow for your clients.  You still need to implement your own views for accessing and authorizing tokens. - -The `django-oauth-plus` package provides simple foundation for classic 'three-legged' oauth flow.  Please refer to [the documentation][django-oauth-plus] for more details. - -## OAuth2Authentication - -This authentication uses [OAuth 2.0][rfc6749] authentication scheme.  OAuth2 is more simple to work with than OAuth1, and provides much better security than simple token authentication.  It is an unauthenticated scheme, and requires you to use an HTTPS connection. - -This authentication class depends on the optional [django-oauth2-provider][django-oauth2-provider] project.  In order to make it work you must install this package and add `provider` and `provider.oauth2` to your `INSTALLED_APPS`: - -    INSTALLED_APPS = ( -        ... -        'provider', -        'provider.oauth2', -    ) - -Then add `OAuth2Authentication` to your global `DEFAULT_AUTHENTICATION_CLASSES` setting: - -    'DEFAULT_AUTHENTICATION_CLASSES': ( -        'rest_framework.authentication.OAuth2Authentication', -    ), - -You must also include the following in your root `urls.py` module: - -    url(r'^oauth2/', include('provider.oauth2.urls', namespace='oauth2')), - -Note that the `namespace='oauth2'` argument is required. - -Finally, sync your database. - -    python manage.py syncdb -    python manage.py migrate - ---- - -**Note:** If you use `OAuth2Authentication` in production you must ensure that your API is only available over `https`. - ---- - -#### Getting started with django-oauth2-provider - -The `OAuth2Authentication` class only provides token verification for requests.  It doesn't provide authorization flow for your clients. - -The OAuth 2 authorization flow is taken care by the [django-oauth2-provider][django-oauth2-provider] dependency.  A walkthrough is given here, but for more details you should refer to [the documentation][django-oauth2-provider-docs]. - -To get started: - -##### 1. Create a client - -You can create a client, either through the shell, or by using the Django admin. - -Go to the admin panel and create a new `Provider.Client` entry.  It will create the `client_id` and `client_secret` properties for you. - -##### 2. Request an access token - -To request an access token, submit a `POST` request to the url `/oauth2/access_token` with the following fields: - -* `client_id` the client id you've just configured at the previous step. -* `client_secret` again configured at the previous step. -* `username` the username with which you want to log in. -* `password` well, that speaks for itself. - -You can use the command line to test that your local configuration is working: - -    curl -X POST -d "client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=password&username=YOUR_USERNAME&password=YOUR_PASSWORD" http://localhost:8000/oauth2/access_token/ - -You should get a response that looks something like this: - -    {"access_token": "<your-access-token>", "scope": "read", "expires_in": 86399, "refresh_token": "<your-refresh-token>"} - -##### 3. Access the API - -The only thing needed to make the `OAuth2Authentication` class work is to insert the `access_token` you've received in the `Authorization` request header. - -The command line to test the authentication looks like: - -    curl -H "Authorization: Bearer <your-access-token>" http://localhost:8000/api/ - -### Alternative OAuth 2 implementations - -Note that [Django OAuth Toolkit][django-oauth-toolkit] is an alternative external package that also includes OAuth 2.0 support for REST framework. - ---- -  # Custom authentication  To implement a custom authentication scheme, subclass `BaseAuthentication` and override the `.authenticate(self, request)` method.  The method should return a two-tuple of `(user, auth)` if authentication succeeds, or `None` otherwise. @@ -392,13 +290,48 @@ The following example will authenticate any incoming request as the user given b  The following third party packages are also available. -## Digest Authentication +## Django OAuth Toolkit -HTTP digest authentication is a widely implemented scheme that was intended to replace HTTP basic authentication, and which provides a simple encrypted authentication mechanism. [Juan Riaza][juanriaza] maintains the [djangorestframework-digestauth][djangorestframework-digestauth] package which provides HTTP digest authentication support for REST framework. +The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support, and works with Python 2.7 and Python 3.3+. The package is maintained by [Evonove][evonove] and uses the excellent [OAuthLib][oauthlib].  The package is well documented, and well supported and is currently our **recommended package for OAuth 2.0 support**. -## Django OAuth Toolkit +#### Installation & configuration + +Install using `pip`. + +    pip install django-oauth-toolkit + +Add the package to your `INSTALLED_APPS` and modify your REST framework settings. -The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support, and works with Python 2.7 and Python 3.3+.  The package is maintained by [Evonove][evonove] and uses the excellent [OAuthLib][oauthlib].  The package is well documented, and comes as a recommended alternative for OAuth 2.0 support. +    INSTALLED_APPS = ( +        ... +        'oauth2_provider', +    ) + +    REST_FRAMEWORK = { +        'DEFAULT_AUTHENTICATION_CLASSES': ( +            'oauth2_provider.ext.rest_framework.OAuth2Authentication', +        ) +    } + +For more details see the [Django REST framework - Getting started][django-oauth-toolkit-getting-started] documentation. + +## Django REST framework OAuth + +The [Django REST framework OAuth][django-rest-framework-oauth] package provides both OAuth1 and OAuth2 support for REST framework. + +This package was previously included directly in REST framework but is now supported and maintained as a third party package. + +#### Installation & configuration + +Install the package using `pip`. + +    pip install djangorestframework-oauth + +For details on configuration and usage see the Django REST framework OAuth documentation for [authentication][django-rest-framework-oauth-authentication] and [permissions][django-rest-framework-oauth-permissions]. + +## Digest Authentication + +HTTP digest authentication is a widely implemented scheme that was intended to replace HTTP basic authentication, and which provides a simple encrypted authentication mechanism. [Juan Riaza][juanriaza] maintains the [djangorestframework-digestauth][djangorestframework-digestauth] package which provides HTTP digest authentication support for REST framework.  ## Django OAuth2 Consumer @@ -431,6 +364,10 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a  [mod_wsgi_official]: http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIPassAuthorization  [custom-user-model]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#specifying-a-custom-user-model  [south-dependencies]: http://south.readthedocs.org/en/latest/dependencies.html +[django-oauth-toolkit-getting-started]: https://django-oauth-toolkit.readthedocs.org/en/latest/rest-framework/getting_started.html +[django-rest-framework-oauth]: http://jpadilla.github.io/django-rest-framework-oauth/ +[django-rest-framework-oauth-authentication]: http://jpadilla.github.io/django-rest-framework-oauth/authentication/ +[django-rest-framework-oauth-permissions]: http://jpadilla.github.io/django-rest-framework-oauth/permissions/  [juanriaza]: https://github.com/juanriaza  [djangorestframework-digestauth]: https://github.com/juanriaza/django-rest-framework-digestauth  [oauth-1.0a]: http://oauth.net/core/1.0a diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index 993134f7..56811ec3 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -61,10 +61,10 @@ In order to alter the style of the response, you could write the following custo      from rest_framework.views import exception_handler -    def custom_exception_handler(exc): +    def custom_exception_handler(exc, context):          # Call REST framework's default exception handler first,          # to get the standard error response. -        response = exception_handler(exc) +        response = exception_handler(exc, context)          # Now add the HTTP status code to the response.          if response is not None: @@ -138,6 +138,14 @@ Raised when an authenticated request fails the permission checks.  By default this exception results in a response with the HTTP status code "403 Forbidden". +## NotFound + +**Signature:** `NotFound(detail=None)` + +Raised when a resource does not exists at the given URL. This exception is equivalent to the standard `Http404` Django exception. + +By default this exception results in a response with the HTTP status code "404 Not Found". +  ## MethodNotAllowed  **Signature:** `MethodNotAllowed(method, detail=None)` @@ -146,6 +154,14 @@ Raised when an incoming request occurs that does not map to a handler method on  By default this exception results in a response with the HTTP status code "405 Method Not Allowed". +## NotAcceptable + +**Signature:** `NotAcceptable(detail=None)` + +Raised when an incoming request occurs with an `Accept` header that cannot be satisfied by any of the available renderers. + +By default this exception results in a response with the HTTP status code "406 Not Acceptable". +  ## UnsupportedMediaType  **Signature:** `UnsupportedMediaType(media_type, detail=None)` diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index 83429292..8ab2edd5 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -6,147 +6,110 @@ source: pagination.py  >  > — [Django documentation][cite] -REST framework includes a `PaginationSerializer` class that makes it easy to return paginated data in a way that can then be rendered to arbitrary media types. +REST framework includes support for customizable pagination styles. This allows you to modify how large result sets are split into individual pages of data. -## Paginating basic data +The pagination API can support either: -Let's start by taking a look at an example from the Django documentation. +* Pagination links that are provided as part of the content of the response. +* Pagination links that are included in response headers, such as `Content-Range` or `Link`. -    from django.core.paginator import Paginator +The built-in styles currently all use links included as part of the content of the response. This style is more accessible when using the browsable API. -    objects = ['john', 'paul', 'george', 'ringo'] -    paginator = Paginator(objects, 2) -    page = paginator.page(1) -    page.object_list -    # ['john', 'paul'] +Pagination is only performed automatically if you're using the generic views or viewsets. If you're using a regular `APIView`, you'll need to call into the pagination API yourself to ensure you return a paginated response. See the source code for the `mixins.ListMixin` and `generics.GenericAPIView` classes for an example. -At this point we've got a page object.  If we wanted to return this page object as a JSON response, we'd need to provide the client with context such as next and previous links, so that it would be able to page through the remaining results. +## Setting the pagination style -    from rest_framework.pagination import PaginationSerializer +The default pagination style may be set globally, using the `DEFAULT_PAGINATION_CLASS` settings key. For example, to use the built-in limit/offset pagination, you would do: -    serializer = PaginationSerializer(instance=page) -    serializer.data -    # {'count': 4, 'next': '?page=2', 'previous': None, 'results': [u'john', u'paul']} - -The `context` argument of the `PaginationSerializer` class may optionally include the request.  If the request is included in the context then the next and previous links returned by the serializer will use absolute URLs instead of relative URLs. - -    request = RequestFactory().get('/foobar') -    serializer = PaginationSerializer(instance=page, context={'request': request}) -    serializer.data -    # {'count': 4, 'next': 'http://testserver/foobar?page=2', 'previous': None, 'results': [u'john', u'paul']} - -We could now return that data in a `Response` object, and it would be rendered into the correct media type. - -## Paginating QuerySets - -Our first example worked because we were using primitive objects.  If we wanted to paginate a queryset or other complex data, we'd need to specify a serializer to use to serialize the result set itself. - -We can do this using the `object_serializer_class` attribute on the inner `Meta` class of the pagination serializer.  For example. - -    class UserSerializer(serializers.ModelSerializer): -        """ -        Serializes user querysets. -        """ -        class Meta: -            model = User -            fields = ('username', 'email') +    REST_FRAMEWORK = { +        'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination' +    } -    class PaginatedUserSerializer(pagination.PaginationSerializer): -        """ -        Serializes page objects of user querysets. -        """ -        class Meta: -            object_serializer_class = UserSerializer +You can also set the pagination class on an individual view by using the `pagination_class` attribute. Typically you'll want to use the same pagination style throughout your API, although you might want to vary individual aspects of the pagination, such as default or maximum page size, on a per-view basis. -We could now use our pagination serializer in a view like this. +## Modifying the pagination style -    @api_view('GET') -    def user_list(request): -        queryset = User.objects.all() -        paginator = Paginator(queryset, 20) +If you want to modify particular aspects of the pagination style, you'll want to override one of the pagination classes, and set the attributes that you want to change. -        page = request.QUERY_PARAMS.get('page') -        try: -            users = paginator.page(page) -        except PageNotAnInteger: -            # If page is not an integer, deliver first page. -            users = paginator.page(1) -        except EmptyPage: -            # If page is out of range (e.g. 9999), -            # deliver last page of results. -            users = paginator.page(paginator.num_pages) +    class LargeResultsSetPagination(PageNumberPagination): +        paginate_by = 1000 +        paginate_by_param = 'page_size' +        max_paginate_by = 10000 -        serializer_context = {'request': request} -        serializer = PaginatedUserSerializer(users, -                                             context=serializer_context) -        return Response(serializer.data) +    class StandardResultsSetPagination(PageNumberPagination): +        paginate_by = 100 +        paginate_by_param = 'page_size' +        max_paginate_by = 1000 -## Pagination in the generic views +You can then apply your new style to a view using the `.pagination_class` attribute: -The generic class based views `ListAPIView` and `ListCreateAPIView` provide pagination of the returned querysets by default.  You can customise this behaviour by altering the pagination style, by modifying the default number of results, by allowing clients to override the page size using a query parameter, or by turning pagination off completely. +    class BillingRecordsView(generics.ListAPIView): +        queryset = Billing.objects.all() +        serializer = BillingRecordsSerializer +        pagination_class = LargeResultsSetPagination -The default pagination style may be set globally, using the `DEFAULT_PAGINATION_SERIALIZER_CLASS`, `PAGINATE_BY`, `PAGINATE_BY_PARAM`, and `MAX_PAGINATE_BY` settings.  For example. +Or apply the style globally, using the `DEFAULT_PAGINATION_CLASS` settings key. For example:      REST_FRAMEWORK = { -        'PAGINATE_BY': 10,                 # Default to 10 -        'PAGINATE_BY_PARAM': 'page_size',  # Allow client to override, using `?page_size=xxx`. -        'MAX_PAGINATE_BY': 100             # Maximum limit allowed when using `?page_size=xxx`. -    } +        'DEFAULT_PAGINATION_CLASS': 'apps.core.pagination.StandardResultsSetPagination'
    } -You can also set the pagination style on a per-view basis, using the `ListAPIView` generic class-based view. +# API Reference -    class PaginatedListView(ListAPIView): -        queryset = ExampleModel.objects.all() -        serializer_class = ExampleModelSerializer -        paginate_by = 10 -        paginate_by_param = 'page_size' -        max_paginate_by = 100 +## PageNumberPagination -Note that using a `paginate_by` value of `None` will turn off pagination for the view. -Note if you use the `PAGINATE_BY_PARAM` settings, you also have to set the `paginate_by_param` attribute in your view to `None` in order to turn off pagination for those requests that contain the `paginate_by_param` parameter. - -For more complex requirements such as serialization that differs depending on the requested media type you can override the `.get_paginate_by()` and `.get_pagination_serializer_class()` methods. +## LimitOffsetPagination  --- -# Custom pagination serializers +# Custom pagination styles + +To create a custom pagination serializer class you should subclass `pagination.BasePagination` and override the `paginate_queryset(self, queryset, request, view=None)` and `get_paginated_response(self, data)` methods: -To create a custom pagination serializer class you should override `pagination.BasePaginationSerializer` and set the fields that you want the serializer to return. +* The `paginate_queryset` method is passed the initial queryset and should return an iterable object that contains only the data in the requested page. +* The `get_paginated_response` method is passed the serialized page data and should return a `Response` instance. -You can also override the name used for the object list field, by setting the `results_field` attribute, which defaults to `'results'`. +Note that the `paginate_queryset` method may set state on the pagination instance, that may later be used by the `get_paginated_response` method.  ## Example -For example, to nest a pair of links labelled 'prev' and 'next', and set the name for the results field to 'objects', you might use something like this. +Let's modify the built-in `PageNumberPagination` style, so that instead of include the pagination links in the body of the response, we'll instead include a `Link` header, in a [similar style to the GitHub API][github-link-pagination]. -    from rest_framework import pagination -    from rest_framework import serializers +    class LinkHeaderPagination(pagination.PageNumberPagination): +        def get_paginated_response(self, data): +            next_url = self.get_next_link()
            previous_url = self.get_previous_link() -    class LinksSerializer(serializers.Serializer): -        next = pagination.NextPageField(source='*') -        prev = pagination.PreviousPageField(source='*') +            if next_url is not None and previous_url is not None: +                link = '<{next_url}; rel="next">, <{previous_url}; rel="prev">' +            elif next_url is not None: +                link = '<{next_url}; rel="next">' +            elif previous_url is not None: +                link = '<{previous_url}; rel="prev">' +            else: +                link = '' -    class CustomPaginationSerializer(pagination.BasePaginationSerializer): -        links = LinksSerializer(source='*')  # Takes the page object as the source -        total_results = serializers.ReadOnlyField(source='paginator.count') +            link = link.format(next_url=next_url, previous_url=previous_url) +            headers = {'Link': link} if link else {} -        results_field = 'objects' +            return Response(data, headers=headers) -## Using your custom pagination serializer +## Using your custom pagination class -To have your custom pagination serializer be used by default, use the `DEFAULT_PAGINATION_SERIALIZER_CLASS` setting: +To have your custom pagination class be used by default, use the `DEFAULT_PAGINATION_CLASS` setting:      REST_FRAMEWORK = { -        'DEFAULT_PAGINATION_SERIALIZER_CLASS': -            'example_app.pagination.CustomPaginationSerializer', +        'DEFAULT_PAGINATION_CLASS': 'my_project.apps.core.pagination.LinkHeaderPagination', +        'PAGINATE_BY': 10      } -Alternatively, to set your custom pagination serializer on a per-view basis, use the `pagination_serializer_class` attribute on a generic class based view: +API responses for list endpoints will now include a `Link` header, instead of including the pagination links as part of the body of the response, for example: -    class PaginatedListView(generics.ListAPIView): -        model = ExampleModel -        pagination_serializer_class = CustomPaginationSerializer -        paginate_by = 10 +--- + +![Link Header][link-header] + +*A custom pagination style, using the 'Link' header'* + +---  # Third party packages @@ -157,5 +120,7 @@ The following third party packages are also available.  The [`DRF-extensions` package][drf-extensions] includes a [`PaginateByMaxMixin` mixin class][paginate-by-max-mixin] that allows your API clients to specify `?page_size=max` to obtain the maximum allowed page size.  [cite]: https://docs.djangoproject.com/en/dev/topics/pagination/ +[github-link-pagination]: https://developer.github.com/guides/traversing-with-pagination/ +[link-header]: ../img/link-header-pagination.png  [drf-extensions]: http://chibisov.github.io/drf-extensions/docs/  [paginate-by-max-mixin]: http://chibisov.github.io/drf-extensions/docs/#paginatebymaxmixin diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index 73e3a705..b68b33be 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -26,26 +26,26 @@ As an example, if you are sending `json` encoded data using jQuery with the [.aj  ## Setting the parsers -The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSES` setting.  For example, the following settings would allow requests with `YAML` content. +The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSES` setting. For example, the following settings would allow only requests with `JSON` content, instead of the default of JSON or form data.      REST_FRAMEWORK = {          'DEFAULT_PARSER_CLASSES': ( -            'rest_framework.parsers.YAMLParser', +            'rest_framework.parsers.JSONParser',          )      }  You can also set the parsers used for an individual view, or viewset,  using the `APIView` class based views. -	from rest_framework.parsers import YAMLParser -	from rest_framework.response import Response +    from rest_framework.parsers import JSONParser +    from rest_framework.response import Response      from rest_framework.views import APIView      class ExampleView(APIView):          """ -        A view that can accept POST requests with YAML content. +        A view that can accept POST requests with JSON content.          """ -        parser_classes = (YAMLParser,) +        parser_classes = (JSONParser,)          def post(self, request, format=None):              return Response({'received data': request.data}) @@ -53,10 +53,10 @@ using the `APIView` class based views.  Or, if you're using the `@api_view` decorator with function based views.      @api_view(['POST']) -    @parser_classes((YAMLParser,)) +    @parser_classes((JSONParser,))      def example_view(request, format=None):          """ -        A view that can accept POST requests with YAML content. +        A view that can accept POST requests with JSON content.          """          return Response({'received data': request.data}) @@ -70,26 +70,6 @@ Parses `JSON` request content.  **.media_type**: `application/json` -## YAMLParser - -Parses `YAML` request content. - -Requires the `pyyaml` package to be installed. - -**.media_type**: `application/yaml` - -## XMLParser - -Parses REST framework's default style of `XML` request content. - -Note that the `XML` markup language is typically used as the base language for more strictly defined domain-specific languages, such as `RSS`, `Atom`, and `XHTML`. - -If you are considering using `XML` for your API, you may want to consider implementing a custom renderer and parser for your specific requirements, and using an existing domain-specific media-type, or creating your own custom XML-based media-type. - -Requires the `defusedxml` package to be installed. - -**.media_type**: `application/xml` -  ## FormParser  Parses HTML form content.  `request.data` will be populated with a `QueryDict` of data. @@ -161,7 +141,7 @@ By default this will include the following keys: `view`, `request`, `args`, `kwa  ## Example -The following is an example plaintext parser that will populate the `request.data` property with a string representing the body of the request.  +The following is an example plaintext parser that will populate the `request.data` property with a string representing the body of the request.      class PlainTextParser(BaseParser):      """ @@ -182,6 +162,48 @@ The following is an example plaintext parser that will populate the `request.dat  The following third party packages are also available. +## YAML + +[REST framework YAML][rest-framework-yaml] provides [YAML][yaml] parsing and rendering support. It was previously included directly in the REST framework package, and is now instead supported as a third-party package. + +#### Installation & configuration + +Install using pip. + +    $ pip install djangorestframework-yaml + +Modify your REST framework settings. + +    REST_FRAMEWORK = { +        'DEFAULT_PARSER_CLASSES': ( +            'rest_framework_yaml.parsers.YAMLParser', +        ), +        'DEFAULT_RENDERER_CLASSES': ( +            'rest_framework_yaml.renderers.YAMLRenderer', +        ), +    } + +## XML + +[REST Framework XML][rest-framework-xml] provides a simple informal XML format. It was previously included directly in the REST framework package, and is now instead supported as a third-party package. + +#### Installation & configuration + +Install using pip. + +    $ pip install djangorestframework-xml + +Modify your REST framework settings. + +    REST_FRAMEWORK = { +        'DEFAULT_PARSER_CLASSES': ( +            'rest_framework_xml.parsers.XMLParser', +        ), +        'DEFAULT_RENDERER_CLASSES': ( +            'rest_framework_xml.renderers.XMLRenderer', +        ), +    } +  ## MessagePack  [MessagePack][messagepack] is a fast, efficient binary serialization format.  [Juan Riaza][juanriaza] maintains the [djangorestframework-msgpack][djangorestframework-msgpack] package which provides MessagePack renderer and parser support for REST framework. @@ -193,6 +215,9 @@ The following third party packages are also available.  [jquery-ajax]: http://api.jquery.com/jQuery.ajax/  [cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion  [upload-handlers]: https://docs.djangoproject.com/en/dev/topics/http/file-uploads/#upload-handlers +[rest-framework-yaml]: http://jpadilla.github.io/django-rest-framework-yaml/ +[rest-framework-xml]: http://jpadilla.github.io/django-rest-framework-xml/ +[yaml]: http://www.yaml.org/  [messagepack]: https://github.com/juanriaza/django-rest-framework-msgpack  [juanriaza]: https://github.com/juanriaza  [vbabiy]: https://github.com/vbabiy diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 743ca435..8731cab0 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -166,21 +166,6 @@ As with `DjangoModelPermissions` you can use custom model permissions by overrid  --- -## TokenHasReadWriteScope - -This permission class is intended for use with either of the `OAuthAuthentication` and `OAuth2Authentication` classes, and ties into the scoping that their backends provide. - -Requests with a safe methods of `GET`, `OPTIONS` or `HEAD` will be allowed if the authenticated token has read permission. - -Requests for `POST`, `PUT`, `PATCH` and `DELETE` will be allowed if the authenticated token has write permission. - -This permission class relies on the implementations of the [django-oauth-plus][django-oauth-plus] and [django-oauth2-provider][django-oauth2-provider] libraries, which both provide limited support for controlling the scope of access tokens: - -* `django-oauth-plus`: Tokens are associated with a `Resource` class which has a `name`, `url` and `is_readonly` properties. -* `django-oauth2-provider`: Tokens are associated with a bitwise `scope` attribute, that defaults to providing bitwise values for `read` and/or `write`. - -If you require more advanced scoping for your API, such as restricting tokens to accessing a subset of functionality of your API then you will need to provide a custom permission class.  See the source of the `django-oauth-plus` or `django-oauth2-provider` package for more details on scoping token access. -  ---  # Custom permissions @@ -268,8 +253,6 @@ The [REST Condition][rest-condition] package is another extension for building c  [objectpermissions]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#handling-object-permissions  [guardian]: https://github.com/lukaszb/django-guardian  [get_objects_for_user]: http://pythonhosted.org/django-guardian/api/guardian.shortcuts.html#get-objects-for-user -[django-oauth-plus]: http://code.larlet.fr/django-oauth-plus -[django-oauth2-provider]: https://github.com/caffeinehit/django-oauth2-provider  [2.2-announcement]: ../topics/2.2-announcement.md  [filtering]: filtering.md  [drf-any-permissions]: https://github.com/kevin-brown/drf-any-permissions diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index 035ec1d2..83ded849 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -18,11 +18,11 @@ For more information see the documentation on [content negotiation][conneg].  ## Setting the renderers -The default set of renderers may be set globally, using the `DEFAULT_RENDERER_CLASSES` setting.  For example, the following settings would use `YAML` as the main media type and also include the self describing API. +The default set of renderers may be set globally, using the `DEFAULT_RENDERER_CLASSES` setting.  For example, the following settings would use `JSON` as the main media type and also include the self describing API.      REST_FRAMEWORK = {          'DEFAULT_RENDERER_CLASSES': ( -            'rest_framework.renderers.YAMLRenderer', +            'rest_framework.renderers.JSONRenderer',              'rest_framework.renderers.BrowsableAPIRenderer',          )      } @@ -31,15 +31,15 @@ You can also set the renderers used for an individual view, or viewset,  using the `APIView` class based views.      from django.contrib.auth.models import User -    from rest_framework.renderers import JSONRenderer, YAMLRenderer +    from rest_framework.renderers import JSONRenderer      from rest_framework.response import Response      from rest_framework.views import APIView      class UserCountView(APIView):          """ -        A view that returns the count of active users, in JSON or YAML. +        A view that returns the count of active users in JSON.          """ -        renderer_classes = (JSONRenderer, YAMLRenderer) +        renderer_classes = (JSONRenderer, )          def get(self, request, format=None):              user_count = User.objects.filter(active=True).count() @@ -49,10 +49,10 @@ using the `APIView` class based views.  Or, if you're using the `@api_view` decorator with function based views.      @api_view(['GET']) -    @renderer_classes((JSONRenderer, JSONPRenderer)) +    @renderer_classes((JSONRenderer,))      def user_count_view(request, format=None):          """ -        A view that returns the count of active users, in JSON or JSONp. +        A view that returns the count of active users in JSON.          """          user_count = User.objects.filter(active=True).count()          content = {'user_count': user_count} @@ -93,72 +93,6 @@ The default JSON encoding style can be altered using the `UNICODE_JSON` and `COM  **.charset**: `None` -## JSONPRenderer - -Renders the request data into `JSONP`.  The `JSONP` media type provides a mechanism of allowing cross-domain AJAX requests, by wrapping a `JSON` response in a javascript callback. - -The javascript callback function must be set by the client including a `callback` URL query parameter.  For example `http://example.com/api/users?callback=jsonpCallback`.  If the callback function is not explicitly set by the client it will default to `'callback'`. - ---- - -**Warning**: If you require cross-domain AJAX requests, you should almost certainly be using the more modern approach of [CORS][cors] as an alternative to `JSONP`.  See the [CORS documentation][cors-docs] for more details. - -The `jsonp` approach is essentially a browser hack, and is [only appropriate for globally  readable API endpoints][jsonp-security], where `GET` requests are unauthenticated and do not require any user permissions. - ---- - -**.media_type**: `application/javascript` - -**.format**: `'.jsonp'` - -**.charset**: `utf-8` - -## YAMLRenderer - -Renders the request data into `YAML`. - -Requires the `pyyaml` package to be installed. - -Note that non-ascii characters will be rendered using `\uXXXX` character escape.  For example: - -    unicode black star: "\u2605" - -**.media_type**: `application/yaml` - -**.format**: `'.yaml'` - -**.charset**: `utf-8` - -## UnicodeYAMLRenderer - -Renders the request data into `YAML`. - -Requires the `pyyaml` package to be installed. - -Note that non-ascii characters will not be character escaped.  For example: - -    unicode black star: ★ - -**.media_type**: `application/yaml` - -**.format**: `'.yaml'` - -**.charset**: `utf-8` - -## XMLRenderer - -Renders REST framework's default style of `XML` response content. - -Note that the `XML` markup language is used typically used as the base language for more strictly defined domain-specific languages, such as `RSS`, `Atom`, and `XHTML`. - -If you are considering using `XML` for your API, you may want to consider implementing a custom renderer and parser for your specific requirements, and using an existing domain-specific media-type, or creating your own custom XML-based media-type. - -**.media_type**: `application/xml` - -**.format**: `'.xml'` - -**.charset**: `utf-8` -  ## TemplateHTMLRenderer  Renders data to HTML, using Django's standard template rendering. @@ -408,13 +342,81 @@ Templates will render with a `RequestContext` which includes the `status_code` a  The following third party packages are also available. +## YAML + +[REST framework YAML][rest-framework-yaml] provides [YAML][yaml] parsing and rendering support. It was previously included directly in the REST framework package, and is now instead supported as a third-party package. + +#### Installation & configuration + +Install using pip. + +    $ pip install djangorestframework-yaml + +Modify your REST framework settings. + +    REST_FRAMEWORK = { +        'DEFAULT_PARSER_CLASSES': ( +            'rest_framework_yaml.parsers.YAMLParser', +        ), +        'DEFAULT_RENDERER_CLASSES': ( +            'rest_framework_yaml.renderers.YAMLRenderer', +        ), +    } + +## XML + +[REST Framework XML][rest-framework-xml] provides a simple informal XML format. It was previously included directly in the REST framework package, and is now instead supported as a third-party package. + +#### Installation & configuration + +Install using pip. + +    $ pip install djangorestframework-xml + +Modify your REST framework settings. + +    REST_FRAMEWORK = { +        'DEFAULT_PARSER_CLASSES': ( +            'rest_framework_xml.parsers.XMLParser', +        ), +        'DEFAULT_RENDERER_CLASSES': ( +            'rest_framework_xml.renderers.XMLRenderer', +        ), +    } + +## JSONP + +[REST framework JSONP][rest-framework-jsonp] provides JSONP rendering support. It was previously included directly in the REST framework package, and is now instead supported as a third-party package. + +--- + +**Warning**: If you require cross-domain AJAX requests, you should generally be using the more modern approach of [CORS][cors] as an alternative to `JSONP`. See the [CORS documentation][cors-docs] for more details. + +The `jsonp` approach is essentially a browser hack, and is [only appropriate for globally readable API endpoints][jsonp-security], where `GET` requests are unauthenticated and do not require any user permissions. + +--- + +#### Installation & configuration + +Install using pip. + +    $ pip install djangorestframework-jsonp + +Modify your REST framework settings. + +    REST_FRAMEWORK = { +        'DEFAULT_RENDERER_CLASSES': ( +            'rest_framework_yaml.renderers.JSONPRenderer', +        ), +    } +  ## MessagePack  [MessagePack][messagepack] is a fast, efficient binary serialization format.  [Juan Riaza][juanriaza] maintains the [djangorestframework-msgpack][djangorestframework-msgpack] package which provides MessagePack renderer and parser support for REST framework.  ## CSV -Comma-separated values are a plain-text tabular data format, that can be easily imported into spreadsheet applications.  [Mjumbe Poe][mjumbewu] maintains the [djangorestframework-csv][djangorestframework-csv] package which provides CSV renderer support for REST framework. +Comma-separated values are a plain-text tabular data format, that can be easily imported into spreadsheet applications. [Mjumbe Poe][mjumbewu] maintains the [djangorestframework-csv][djangorestframework-csv] package which provides CSV renderer support for REST framework.  ## UltraJSON @@ -424,7 +426,6 @@ Comma-separated values are a plain-text tabular data format, that can be easily  [djangorestframework-camel-case] provides camel case JSON renderers and parsers for REST framework.  This allows serializers to use Python-style underscored field names, but be exposed in the API as Javascript-style camel case field names.  It is maintained by [Vitaly Babiy][vbabiy]. -  ## Pandas (CSV, Excel, PNG)  [Django REST Pandas] provides a serializer and renderers that support additional data processing and output via the [Pandas] DataFrame API.  Django REST Pandas includes renderers for Pandas-style CSV files, Excel workbooks (both `.xls` and `.xlsx`), and a number of [other formats]. It is maintained by [S. Andrew Sheppard][sheppard] as part of the [wq Project][wq]. @@ -433,20 +434,25 @@ Comma-separated values are a plain-text tabular data format, that can be easily  [cite]: https://docs.djangoproject.com/en/dev/ref/template-response/#the-rendering-process  [conneg]: content-negotiation.md  [browser-accept-headers]: http://www.gethifi.com/blog/browser-rest-http-accept-headers -[rfc4627]: http://www.ietf.org/rfc/rfc4627.txt -[cors]: http://www.w3.org/TR/cors/ -[cors-docs]: ../topics/ajax-csrf-cors.md -[jsonp-security]: http://stackoverflow.com/questions/613962/is-jsonp-safe-to-use  [testing]: testing.md  [HATEOAS]: http://timelessrepo.com/haters-gonna-hateoas  [quote]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven  [application/vnd.github+json]: http://developer.github.com/v3/media/  [application/vnd.collection+json]: http://www.amundsen.com/media-types/collection/  [django-error-views]: https://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views +[rest-framework-jsonp]: http://jpadilla.github.io/django-rest-framework-jsonp/ +[cors]: http://www.w3.org/TR/cors/ +[cors-docs]: http://www.django-rest-framework.org/topics/ajax-csrf-cors/ +[jsonp-security]: http://stackoverflow.com/questions/613962/is-jsonp-safe-to-use +[rest-framework-yaml]: http://jpadilla.github.io/django-rest-framework-yaml/ +[rest-framework-xml]: http://jpadilla.github.io/django-rest-framework-xml/  [messagepack]: http://msgpack.org/  [juanriaza]: https://github.com/juanriaza  [mjumbewu]: https://github.com/mjumbewu  [vbabiy]: https://github.com/vbabiy +[rest-framework-yaml]: http://jpadilla.github.io/django-rest-framework-yaml/ +[rest-framework-xml]: http://jpadilla.github.io/django-rest-framework-xml/ +[yaml]: http://www.yaml.org/  [djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack  [djangorestframework-csv]: https://github.com/mjumbewu/django-rest-framework-csv  [ultrajson]: https://github.com/esnme/ultrajson diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index f88ec51f..9a9d5032 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -457,7 +457,7 @@ To do so, open the Django shell, using `python manage.py shell`, then import the          name = CharField(allow_blank=True, max_length=100, required=False)          owner = PrimaryKeyRelatedField(queryset=User.objects.all()) -## Specifying which fields should be included +## Specifying which fields to include  If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`. @@ -499,7 +499,7 @@ You can add extra fields to a `ModelSerializer` or override the default fields b  Extra fields can correspond to any property or callable on the model. -## Specifying which fields should be read-only +## Specifying read only fields  You may wish to specify multiple fields as read-only. Instead of adding each field explicitly with the `read_only=True` attribute, you may use the shortcut Meta option, `read_only_fields`. @@ -528,7 +528,7 @@ Please review the [Validators Documentation](/api-guide/validators/) for details  --- -## Specifying additional keyword arguments for fields. +## Additional keyword arguments  There is also a shortcut allowing you to specify arbitrary additional keyword arguments on fields, using the `extra_kwargs` option. Similarly to `read_only_fields` this means you do not need to explicitly declare the field on the serializer. @@ -567,6 +567,63 @@ The inner `Meta` class on serializers is not inherited from parent classes by de  Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly. +## Customizing field mappings + +The ModelSerializer class also exposes an API that you can override in order to alter how serializer fields are automatically determined when instantiating the serializer. + +Normally if a `ModelSerializer` does not generate the fields you need by default the you should either add them to the class explicitly, or simply use a regular `Serializer` class instead. However in some cases you may want to create a new base class that defines how the serializer fields are created for any given model. + +### `.serializer_field_mapping` + +A mapping of Django model classes to REST framework serializer classes. You can override this mapping to alter the default serializer classes that should be used for each model class. + +### `.serializer_relational_field` + +This property should be the serializer field class, that is used for relational fields by default. For `ModelSerializer` this defaults to `PrimaryKeyRelatedField`. For `HyperlinkedModelSerializer` this defaults to `HyperlinkedRelatedField`. + +### The field_class and field_kwargs API + +The following methods are called to determine the class and keyword arguments for each field that should be automatically included on the serializer. Each of these methods should return a two tuple of `(field_class, field_kwargs)`. + +### `.build_standard_field(self, field_name, model_field)` + +Called to generate a serializer field that maps to a standard model field. + +The default implementation returns a serializer class based on the `serializer_field_mapping` attribute. + +### `.build_relational_field(self, field_name, relation_info)` + +Called to generate a serializer field that maps to a relational model field. + +The default implementation returns a serializer class based on the `serializer_relational_field` attribute. + +The `relation_info` argument is a named tuple, that contains `model_field`, `related_model`, `to_many` and `has_through_model` properties. + +### `.build_nested_field(self, field_name, relation_info, nested_depth)` + +Called to generate a serializer field that maps to a relational model field, when the `depth` option has been set. + +The default implementation dynamically creates a nested serializer class based on either `ModelSerializer` or `HyperlinkedModelSerializer`. + +The `nested_depth` will be the value of the `depth` option, minus one. + +The `relation_info` argument is a named tuple, that contains `model_field`, `related_model`, `to_many` and `has_through_model` properties. + +### `.build_property_field(self, field_name, model_class)` + +Called to generate a serializer field that maps to a property or zero-argument method on the model class. + +The default implementation returns a `ReadOnlyField` class. + +### `.build_url_field(self, field_name, model_class)` + +Called to generate a serializer field for the serializer's own `url` field. The default implementation returns a `HyperlinkedIdentityField` class. + +### `.build_unknown_field(self, field_name, model_class)` + +Called when the field name did not map to any model field or model property. +The default implementation raises an error, although subclasses may customize this behavior. +  ---  # HyperlinkedModelSerializer diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 9005511b..5af429d1 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -12,10 +12,10 @@ For example your project's `settings.py` file might include something like this:      REST_FRAMEWORK = {          'DEFAULT_RENDERER_CLASSES': ( -            'rest_framework.renderers.YAMLRenderer', +            'rest_framework.renderers.JSONRenderer',          ),          'DEFAULT_PARSER_CLASSES': ( -            'rest_framework.parsers.YAMLParser', +            'rest_framework.parsers.JSONParser',          )      } @@ -166,6 +166,28 @@ Default: `ordering`  --- +## Versioning settings + +#### DEFAULT_VERSION + +The value that should be used for `request.version` when no versioning information is present. + +Default: `None` + +#### ALLOWED_VERSIONS + +If set, this value will restrict the set of versions that may be returned by the versioning scheme, and will raise an error if the provided version if not in this set. + +Default: `None` + +#### VERSION_PARAMETER + +The string that should used for any versioning parameters, such as in the media type or URL query parameters. + +Default: `'version'` + +--- +  ## Authentication settings  *The following settings control the behavior of unauthenticated requests.* @@ -393,7 +415,7 @@ This setting can be changed to support error responses other than the default `{  This should be a function with the following signature: -    exception_handler(exc) +    exception_handler(exc, context)  * `exc`: The exception. diff --git a/docs/api-guide/testing.md b/docs/api-guide/testing.md index d059fdab..cd8c7820 100644 --- a/docs/api-guide/testing.md +++ b/docs/api-guide/testing.md @@ -255,14 +255,14 @@ The default format used to make test requests may be set using the `TEST_REQUEST  If you need to test requests using something other than multipart or json requests, you can do so by setting the `TEST_REQUEST_RENDERER_CLASSES` setting. -For example, to add support for using `format='yaml'` in test requests, you might have something like this in your `settings.py` file. +For example, to add support for using `format='html'` in test requests, you might have something like this in your `settings.py` file.      REST_FRAMEWORK = {          ...          'TEST_REQUEST_RENDERER_CLASSES': (              'rest_framework.renderers.MultiPartRenderer',              'rest_framework.renderers.JSONRenderer', -            'rest_framework.renderers.YAMLRenderer' +            'rest_framework.renderers.TemplateHTMLRenderer'          )      } diff --git a/docs/api-guide/versioning.md b/docs/api-guide/versioning.md new file mode 100644 index 00000000..7463f190 --- /dev/null +++ b/docs/api-guide/versioning.md @@ -0,0 +1,205 @@ +source: versioning.py + +# Versioning + +> Versioning an interface is just a "polite" way to kill deployed clients. +>  +> — [Roy Fielding][cite]. + +API versioning allows you to alter behavior between different clients. REST framework provides for a number of different versioning schemes. + +Versioning is determined by the incoming client request, and may either be based on the request URL, or based on the request headers. + +There are a number of valid approaches to approaching versioning. [Non-versioned systems can also be appropriate][roy-fielding-on-versioning], particularly if you're engineering for very long-term systems with multiple clients outside of your control. + +## Versioning with REST framework + +When API versioning is enabled, the `request.version` attribute will contain a string that corresponds to the version requested in the incoming client request. + +By default, versioning is not enabled, and `request.version` will always return `None`. + +#### Varying behavior based on the version + +How you vary the API behavior is up to you, but one example you might typically want is to switch to a different serialization style in a newer version. For example: + +    def get_serializer_class(self): +        if self.request.version == 'v1': +            return AccountSerializerVersion1 +        return AccountSerializer + +#### Reversing URLs for versioned APIs + +The `reverse` function included by REST framework ties in with the versioning scheme. You need to make sure to include the current `request` as a keyword argument, like so. + +    reverse('bookings-list', request=request) + +The above function will apply any URL transformations appropriate to the request version. For example: + +* If `NamespacedVersioning` was being used, and the API version was 'v1', then the URL lookup used would be `'v1:bookings-list'`, which might resolve to a URL like `http://example.org/v1/bookings/`. +* If `QueryParameterVersioning` was being used, and the API version was `1.0`, then the returned URL might be something like `http://example.org/bookings/?version=1.0` + +#### Versioned APIs and hyperlinked serializers + +When using hyperlinked serialization styles together with a URL based versioning scheme make sure to include the request as context to the serializer. + +    def get(self, request): +        queryset = Booking.objects.all() +        serializer = BookingsSerializer(queryset, many=True, context={'request': request}) +        return Response({'all_bookings': serializer.data}) + +Doing so will allow any returned URLs to include the appropriate versioning. + +## Configuring the versioning scheme + +The versioning scheme is defined by the `DEFAULT_VERSIONING_CLASS` settings key. + +    REST_FRAMEWORK = { +        'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning' +    } + +Unless it is explicitly set, the value for `DEFAULT_VERSIONING_CLASS` will be `None`. In this case the `request.version` attribute will always return `None`. + +You can also set the versioning scheme on an individual view. Typically you won't need to do this, as it makes more sense to have a single versioning scheme used globally. If you do need to do so, use the `versioning_class` attribute. + +    class ProfileList(APIView): +        versioning_class = versioning.QueryParameterVersioning + +#### Other versioning settings + +The following settings keys are also used to control versioning: + +* `DEFAULT_VERSION`. The value that should be used for `request.version` when no versioning information is present. Defaults to `None`. +* `ALLOWED_VERSIONS`. If set, this value will restrict the set of versions that may be returned by the versioning scheme, and will raise an error if the provided version if not in this set. Defaults to `None`. +* `VERSION_PARAMETER`. The string that should used for any versioning parameters, such as in the media type or URL query parameters. Defaults to `'version'`. + +--- + +# API Reference + +## AcceptHeaderVersioning + +This scheme requires the client to specify the version as part of the media type in the `Accept` header. The version is included as a media type parameter, that supplements the main media type. + +Here's an example HTTP request using the accept header versioning style. + +    GET /bookings/ HTTP/1.1 +    Host: example.com +    Accept: application/json; version=1.0 + +In the example request above `request.version` attribute would return the string `'1.0'`. + +Versioning based on accept headers is [generally considered][klabnik-guidelines] as [best practice][heroku-guidelines], although other styles may be suitable depending on your client requirements. + +#### Using accept headers with vendor media types + +Strictly speaking the `json` media type is not specified as [including additional parameters][json-parameters]. If you are building a well-specified public API you might consider using a [vendor media type][vendor-media-type]. To do so, configure your renderers to use a JSON based renderer with a custom media type: + +    class BookingsAPIRenderer(JSONRenderer): +        media_type = 'application/vnd.megacorp.bookings+json' + +Your client requests would now look like this: + +    GET /bookings/ HTTP/1.1 +    Host: example.com +    Accept: application/vnd.megacorp.bookings+json; version=1.0 + +## URLParameterVersioning + +This scheme requires the client to specify the version as part of the URL path. + +    GET /v1/bookings/ HTTP/1.1 +    Host: example.com +    Accept: application/json + +Your URL conf must include a pattern that matches the version with a `'version'` keyword argument, so that this information is available to the versioning scheme. + +    urlpatterns = [ +        url( +            r'^(?P<version>{v1,v2})/bookings/$', +            bookings_list, +            name='bookings-list' +        ), +        url( +            r'^(?P<version>{v1,v2})/bookings/(?P<pk>[0-9]+)/$', +            bookings_detail, +            name='bookings-detail' +        ) +    ] + +## NamespaceVersioning + +To the client, this scheme is the same as `URLParameterVersioning`. The only difference is how it is configured in your Django application, as it uses URL namespacing, instead of URL keyword arguments. + +    GET /v1/something/ HTTP/1.1 +    Host: example.com +    Accept: application/json + +With this scheme the `request.version` attribute is determined based on the `namespace` that matches the incoming request path. + +In the following example we're giving a set of views two different possible URL prefixes, each under a different namespace: + +    # bookings/urls.py +    urlpatterns = [ +        url(r'^$', bookings_list, name='bookings-list'), +        url(r'^(?P<pk>[0-9]+)/$', bookings_detail, name='bookings-detail') +    ] + +    # urls.py +    urlpatterns = [ +        url(r'^v1/bookings/', include('bookings.urls', namespace='v1')), +        url(r'^v2/bookings/', include('bookings.urls', namespace='v2')) +    ] + +Both `URLParameterVersioning` and `NamespaceVersioning` are reasonable if you just need a simple versioning scheme. The `URLParameterVersioning` approach might be better suitable for small ad-hoc projects, and the `NaemspaceVersioning` is probably easier to manage for larger projects. + +## HostNameVersioning + +The hostname versioning scheme requires the client to specify the requested version as part of the hostname in the URL. + +For example the following is an HTTP request to the `http://v1.example.com/bookings/` URL: + +    GET /bookings/ HTTP/1.1 +    Host: v1.example.com +    Accept: application/json + +By default this implementation expects the hostname to match this simple regular expression: + +    ^([a-zA-Z0-9]+)\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$ + +Note that the first group is enclosed in brackets, indicating that this is the matched portion of the hostname. + +The `HostNameVersioning` scheme can be awkward to use in debug mode as you will typically be accessing a raw IP address such as `127.0.0.1`. There are various online services which you to [access localhost with a custom subdomain][lvh] which you may find helpful in this case. + +Hostname based versioning can be particularly useful if you have requirements to route incoming requests to different servers based on the version, as you can configure different DNS records for different API versions. + +## QueryParameterVersioning + +This scheme is a simple style that includes the version as a query parameter in the URL. For example: + +    GET /something/?version=0.1 HTTP/1.1 +    Host: example.com +    Accept: application/json + +--- + +# Custom versioning schemes + +To implement a custom versioning scheme, subclass `BaseVersioning` and override the `.determine_version` method. + +## Example + +The following example uses a custom `X-API-Version` header to determine the requested version. + +    class XAPIVersionScheme(versioning.BaseVersioning): +        def determine_version(self, request, *args, **kwargs): +            return request.META.get('HTTP_X_API_VERSION', None) + +If your versioning scheme is based on the request URL, you will also want to alter how versioned URLs are determined. In order to do so you should override the `.reverse()` method on the class. See the source code for examples. + +[cite]: http://www.slideshare.net/evolve_conference/201308-fielding-evolve/31 +[roy-fielding-on-versioning]: http://www.infoq.com/articles/roy-fielding-on-versioning +[klabnik-guidelines]: http://blog.steveklabnik.com/posts/2011-07-03-nobody-understands-rest-or-http#i_want_my_api_to_be_versioned +[heroku-guidelines]: https://github.com/interagent/http-api-design#version-with-accepts-header +[json-parameters]: http://tools.ietf.org/html/rfc4627#section-6 +[vendor-media-type]: http://en.wikipedia.org/wiki/Internet_media_type#Vendor_tree +[lvh]: https://reinteractive.net/posts/199-developing-and-testing-rails-applications-with-subdomains diff --git a/docs/img/link-header-pagination.png b/docs/img/link-header-pagination.pngBinary files differ new file mode 100644 index 00000000..d3c556a4 --- /dev/null +++ b/docs/img/link-header-pagination.png diff --git a/docs/index.md b/docs/index.md index d40f8972..efd99a4e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -28,13 +28,12 @@ For more details see the [3.0 release notes][3.0-announcement].  <img alt="Django REST Framework" title="Logo by Jake 'Sid' Smith" src="img/logo.png" width="600px" style="display: block; margin: 0 auto 0 auto">  </p> -  Django REST framework is a powerful and flexible toolkit that makes it easy to build Web APIs.  Some reasons you might want to use REST framework:  * The [Web browsable API][sandbox] is a huge usability win for your developers. -* [Authentication policies][authentication] including [OAuth1a][oauth1-section] and [OAuth2][oauth2-section] out of the box. +* [Authentication policies][authentication] including packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section].  * [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources.  * Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers].  * [Extensive documentation][index], and [great community support][group]. @@ -56,15 +55,9 @@ REST framework requires the following:  The following packages are optional:  * [Markdown][markdown] (2.1.0+) - Markdown support for the browsable API. -* [PyYAML][yaml] (3.10+) - YAML content-type support. -* [defusedxml][defusedxml] (0.3+) - XML content-type support.  * [django-filter][django-filter] (0.5.4+) - Filtering support. -* [django-oauth-plus][django-oauth-plus] (2.0+) and [oauth2][oauth2] (1.5.211+) - OAuth 1.0a support. -* [django-oauth2-provider][django-oauth2-provider] (0.2.3+) - OAuth 2.0 support.  * [django-guardian][django-guardian] (1.1.1+) - Object level permissions support. -**Note**: The `oauth2` Python package is badly misnamed, and actually provides OAuth 1.0a support.  Also note that packages required for both OAuth 1.0a, and OAuth 2.0 are not yet Python 3 compatible. -  ## Installation  Install using `pip`, including any optional packages you want... @@ -180,6 +173,7 @@ The API guide is your complete reference manual to all the functionality provide  * [Throttling][throttling]  * [Filtering][filtering]  * [Pagination][pagination] +* [Versioning][versioning]  * [Content negotiation][contentnegotiation]  * [Metadata][metadata]  * [Format suffixes][formatsuffixes] @@ -263,18 +257,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  [mozilla]: http://www.mozilla.org/en-US/about/  [eventbrite]: https://www.eventbrite.co.uk/about/  [markdown]: http://pypi.python.org/pypi/Markdown/ -[yaml]: http://pypi.python.org/pypi/PyYAML -[defusedxml]: https://pypi.python.org/pypi/defusedxml  [django-filter]: http://pypi.python.org/pypi/django-filter -[oauth2]: https://github.com/simplegeo/python-oauth2 -[django-oauth-plus]: https://bitbucket.org/david/django-oauth-plus/wiki/Home -[django-oauth2-provider]: https://github.com/caffeinehit/django-oauth2-provider  [django-guardian]: https://github.com/lukaszb/django-guardian  [0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X  [image]: img/quickstart.png  [index]: . -[oauth1-section]: api-guide/authentication#oauthauthentication -[oauth2-section]: api-guide/authentication#oauth2authentication +[oauth1-section]: api-guide/authentication/#django-rest-framework-oauth +[oauth2-section]: api-guide/authentication/#django-oauth-toolkit  [serializer-section]: api-guide/serializers#serializers  [modelserializer-section]: api-guide/serializers#modelserializer  [functionview-section]: api-guide/views#function-based-views @@ -305,6 +294,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  [throttling]: api-guide/throttling.md  [filtering]: api-guide/filtering.md  [pagination]: api-guide/pagination.md +[versioning]: api-guide/versioning.md  [contentnegotiation]: api-guide/content-negotiation.md  [metadata]: api-guide/metadata.md  [formatsuffixes]: api-guide/format-suffixes.md @@ -315,6 +305,7 @@ 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 +[internationalization]: topics/documenting-your-api.md  [ajax-csrf-cors]: topics/ajax-csrf-cors.md  [browser-enhancements]: topics/browser-enhancements.md  [browsableapi]: topics/browsable-api.md diff --git a/docs/topics/3.1-announcement.md b/docs/topics/3.1-announcement.md new file mode 100644 index 00000000..a0ad9829 --- /dev/null +++ b/docs/topics/3.1-announcement.md @@ -0,0 +1,7 @@ +# Versioning + +# Pagination + +# Internationalization + +# ModelSerializer API diff --git a/docs/topics/internationalization.md b/docs/topics/internationalization.md new file mode 100644 index 00000000..fdde6c43 --- /dev/null +++ b/docs/topics/internationalization.md @@ -0,0 +1,72 @@ +# Internationalization + +> Supporting internationalization is not optional. It must be a core feature. +> +> — [Jannis Leidel, speaking at Django Under the Hood, 2015][cite]. + +REST framework ships with translatable error messages. You can make these appear in your language enabling [Django's standard translation mechanisms][django-translation]. + +Doing so will allow you to: + +* Select a language other than English as the default, using the standard `LANGUAGE_CODE` Django setting. +* Allow clients to choose a language themselves, using the `LocaleMiddleware` included with Django. A typical usage for API clients would be to include an `Accept-Language` request header. + +Note that the translations only apply to the error strings themselves. The format of error messages, and the keys of field names will remain the same. An example `400 Bad Request` response body might look like this: + +    {"detail": {"username": ["Esse campo deve ser unico."]}} + +If you want to use different string for parts of the response such as `detail` and `non_field_errors` then you can modify this behavior by using a [custom exception handler][custom-exception-handler]. + +## Adding new translations + +REST framework translations are managed online using [Transifex][transifex-project]. You can use the Transifex service to add new translation languages. The maintenance team will then ensure that these translation strings are included in the REST framework package. + +Sometimes you may need to add translation strings to your project locally. You may need to do this if: + +* You want to use REST Framework in a language which has not been translated yet on Transifex. +* Your project includes custom error messages, which are not part of REST framework's default translation strings. + +#### Translating a new language locally + +This guide assumes you are already familiar with how to translate a Django app.  If you're not, start by reading [Django's translation docs][django-translation]. + +If you're translating a new language you'll need to translate the existing REST framework error messages: + +1. Make a new folder where you want to store the internationalization resources. Add this path to your [`LOCALE_PATHS`][django-locale-paths] setting. + +2. Now create a subfolder for the language you want to translate. The folder should be named using [locale name][django-locale-name] notation. For example: `de`, `pt_BR`, `es_AR`. + +3. Now copy the [base translations file][django-po-source] from the REST framework source code into your translations folder. + +4. Edit the `django.po` file you've just copied, translating all the error messages. + +5. Run `manage.py compilemessages -l pt_BR` to make the translations  +available for Django to use. You should see a message like `processing file django.po in <...>/locale/pt_BR/LC_MESSAGES`. + +6. Restart your development server to see the changes take effect. + +If you're only translating custom error messages that exist inside your project codebase you don't need to copy the REST framework source `django.po` file into a `LOCALE_PATHS` folder, and can instead simply run Django's standard `makemessages` process. + +## How the language is determined + +If you want to allow per-request language preferences you'll need to include `django.middleware.locale.LocaleMiddleware` in your `MIDDLEWARE_CLASSES` setting. + +You can find more information on how the language preference is determined in the [Django documentation][django-language-preference]. For reference, the method is: + +1. First, it looks for the language prefix in the requested URL. +2. Failing that, it looks for the `LANGUAGE_SESSION_KEY` key in the current user’s session. +3. Failing that, it looks for a cookie. +4. Failing that, it looks at the `Accept-Language` HTTP header. +5. Failing that, it uses the global `LANGUAGE_CODE` setting. + +For API clients the most appropriate of these will typically be to use the `Accept-Language` header; Sessions and cookies will not be available unless using session authentication, and generally better practice to prefer an `Accept-Language` header for API clients rather than using language URL prefixes. + +[cite]: http://youtu.be/Wa0VfS2q94Y +[django-translation]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation +[custom-exception-handler]: ../api-guide/exceptions.md#custom-exception-handling
 +[transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/ +[django-po-source]: https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/rest_framework/locale/en_US/LC_MESSAGES/django.po  +[django-language-preference]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#how-django-discovers-language-preference +[django-locale-paths]: https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-LOCALE_PATHS +[django-locale-name]: https://docs.djangoproject.com/en/1.7/topics/i18n/#term-locale-name +[contributing]: ../../CONTRIBUTING.md diff --git a/docs/topics/project-management.md b/docs/topics/project-management.md index cd0d8cd3..2a54fb94 100644 --- a/docs/topics/project-management.md +++ b/docs/topics/project-management.md @@ -65,10 +65,11 @@ The following template should be used for the description of the issue, and serv  Team members have the following responsibilities. -* Add triage labels and milestones to tickets.  * Close invalid or resolved tickets. +* Add triage labels and milestones to tickets.  * Merge finalized pull requests.  * Build and deploy the documentation, using `mkdocs gh-deploy`. +* Build and update the included translation packs.  Further notes for maintainers: @@ -116,6 +117,55 @@ When pushing the release to PyPI ensure that your environment has been installed  --- +## Translations + +The maintenance team are responsible for managing the translation packs include in REST framework. Translating the source strings into multiple languages is managed through the [transifex service][transifex-project]. + +### Managing Transifex + +The [official Transifex client][transifex-client] is used to upload and download translations to Transifex. The client is installed using pip: + +    pip install transifex-client + +To use it you'll need a login to Transifex which has a password, and you'll need to have administrative access to the Transifex project. You'll need to create a `~/.transifexrc` file which contains your credentials. + +    [https://www.transifex.com] +    username = *** +    token = *** +    password = *** +    hostname = https://www.transifex.com + +### Upload new source files + +When any user visible strings are changed, they should be uploaded to Transifex so that the translators can start to translate them. To do this, just run: + +    # 1. Update the source django.po file, which is the US English version. +    cd rest_framework +    django-admin.py makemessages -l en_US +    # 2. Push the source django.po file to Transifex. +    cd .. +    tx push -s + +When pushing source files, Transifex will update the source strings of a resource to match those from the new source file. + +Here's how differences between the old and new source files will be handled: + +* New strings will be added. +* Modified strings will be added as well. +* Strings which do not exist in the new source file will be removed from the database, along with their translations. If that source strings gets re-added later then [Transifex Translation Memory][translation-memory] will automatically include the translation string. + +### Download translations + +When a translator has finished translating their work needs to be downloaded from Transifex into the REST framework repository. To do this, run: + +    # 3. Pull the translated django.po files from Transifex. +    tx pull -a +    cd rest_framework +    # 4. Compile the binary .mo files for all supported languages. +    django-admin.py compilemessages + +--- +  ## Project ownership  The PyPI package is owned by `@tomchristie`. As a backup `@j4mie` also has ownership of the package. @@ -134,6 +184,9 @@ The following issues still need to be addressed:  [bus-factor]: http://en.wikipedia.org/wiki/Bus_factor  [un-triaged]: https://github.com/tomchristie/django-rest-framework/issues?q=is%3Aopen+no%3Alabel +[transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/ +[transifex-client]: https://pypi.python.org/pypi/transifex-client +[translation-memory]: http://docs.transifex.com/guides/tm#let-tm-automatically-populate-translations  [github-org]: https://github.com/tomchristie/django-rest-framework/issues/2162  [sandbox]: http://restframework.herokuapp.com/  [mailing-list]: https://groups.google.com/forum/#!forum/django-rest-framework diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md index c0426969..4ca4e264 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -92,7 +92,7 @@ Here is the view for an individual snippet, in the `views.py` module.  This should all feel very familiar - it is not a lot different from working with regular Django views. -Notice that we're no longer explicitly tying our requests or responses to a given content type.  `request.data` can handle incoming `json` requests, but it can also handle `yaml` and other formats.  Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us. +Notice that we're no longer explicitly tying our requests or responses to a given content type.  `request.data` can handle incoming `json` requests, but it can also handle other formats.  Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us.  ## Adding optional format suffixes to our URLs | 
