From cab4a2a5ad17545ac435785bf55b0b3a6c8f932c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 17 Oct 2012 15:41:57 +0100 Subject: Split up doc sections more cleanly --- docs/api-guide/exceptions.md | 4 ++++ docs/api-guide/permissions.md | 5 +++++ docs/api-guide/settings.md | 4 ++++ docs/api-guide/throttling.md | 4 ++++ docs/api-guide/views.md | 4 ++-- 5 files changed, 19 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index c3bdb7b9..33cf1ca8 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -33,6 +33,10 @@ Might recieve an error response indicating that the `DELETE` method is not allow {"detail": "Method 'DELETE' not allowed."} +--- + +# API Reference + ## APIException **Signature:** `APIException(detail=None)` diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index eb290849..b25b52be 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -54,6 +54,8 @@ Or, if you're using the `@api_view` decorator with function based views. } return Response(content) +--- + # API Reference ## IsAuthenticated @@ -88,12 +90,15 @@ To use custom model permissions, override `DjangoModelPermissions` and set the ` The `DjangoModelPermissions` class also supports object-level permissions. Third-party authorization backends such as [django-guardian][guardian] that provide object-level permissions should work just fine with `DjangoModelPermissions` without any custom configuration required. +--- + # Custom permissions To implement a custom permission, override `BasePermission` and implement the `.has_permission(self, request, view, obj=None)` method. The method should return `True` if the request should be granted access, and `False` otherwise. + [cite]: https://developer.apple.com/library/mac/#documentation/security/Conceptual/AuthenticationAndAuthorizationGuide/Authorization/Authorization.html [authentication]: authentication.md [throttling]: throttling.md diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index f473128e..84acd797 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -30,6 +30,10 @@ you should use the `api_settings` object. For example. The `api_settings` object will check for any user-defined settings, and otherwise fallback to the default values. Any setting that uses string import paths to refer to a class will automatically import and return the referenced class, instead of the string literal. +--- + +# API Reference + ## DEFAULT_RENDERERS A list or tuple of renderer classes, that determines the default set of renderers that may be used when returning a `Response` object. diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index 3fb95ae3..22e34187 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -63,6 +63,8 @@ Or, if you're using the `@api_view` decorator with function based views. } return Response(content) +--- + # API Reference ## AnonRateThrottle @@ -144,6 +146,8 @@ For example, given the following views... User requests to either `ContactListView` or `ContactDetailView` would be restricted to a total of 1000 requests per-day. User requests to `UploadView` would be restricted to 20 requests per day. +--- + # Custom throttles To create a custom throttle, override `BaseThrottle` and implement `.allow_request(request, view)`. The method should return `True` if the request should be allowed, and `False` otherwise. diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md index cbfa2e28..77349252 100644 --- a/docs/api-guide/views.md +++ b/docs/api-guide/views.md @@ -33,8 +33,8 @@ For example: """ Return a list of all users. """ - users = [user.username for user in User.objects.all()] - return Response(users) + usernames = [user.username for user in User.objects.all()] + return Response(usernames) ## API policy attributes -- cgit v1.2.3 From 6717d654d0bbfdfca4aaea84a5b4814c4e5f7567 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 17 Oct 2012 21:57:23 +0100 Subject: Added @rdobson. Thanks! --- docs/topics/credits.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 3f4b9f0d..6df99237 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -47,6 +47,7 @@ The following people have helped make REST framework great. * Mattbo - [mattbo] * Max Hurl - [maximilianhurl] * Tomi Pajunen - [eofs] +* Rob Dobson - [rdobson] Many thanks to everyone who's contributed to the project. @@ -124,4 +125,5 @@ To contact the author directly: [j4mie]: https://github.com/j4mie [mattbo]: https://github.com/mattbo [maximilianhurl]: https://github.com/maximilianhurl -[eofs]: https://github.com/eofs \ No newline at end of file +[eofs]: https://github.com/eofs +[rdobson]: https://github.com/rdobson -- cgit v1.2.3 From 99d48f90030d174ef80498b48f56af6489865f0d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 17 Oct 2012 22:07:56 +0100 Subject: Drop .parse_string_or_stream() - keep API minimal. --- docs/api-guide/parsers.md | 10 +++++----- docs/tutorial/1-serialization.md | 7 +++++-- 2 files changed, 10 insertions(+), 7 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index 4f145ba3..a950c0e0 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -91,11 +91,11 @@ You will typically want to use both `FormParser` and `MultiPartParser` together # Custom parsers -To implement a custom parser, you should override `BaseParser`, set the `.media_type` property, and implement the `.parse_stream(self, stream, parser_context)` method. +To implement a custom parser, you should override `BaseParser`, set the `.media_type` property, and implement the `.parse(self, stream, parser_context)` method. The method should return the data that will be used to populate the `request.DATA` property. -The arguments passed to `.parse_stream()` are: +The arguments passed to `.parse()` are: ### stream @@ -116,7 +116,7 @@ The following is an example plaintext parser that will populate the `request.DAT media_type = 'text/plain' - def parse_stream(self, stream, parser_context=None): + def parse(self, stream, parser_context=None): """ Simply return a string representing the body of the request. """ @@ -124,7 +124,7 @@ The following is an example plaintext parser that will populate the `request.DAT ## Uploading file content -If your custom parser needs to support file uploads, you may return a `DataAndFiles` object from the `.parse_stream()` method. `DataAndFiles` should be instantiated with two arguments. The first argument will be used to populate the `request.DATA` property, and the second argument will be used to populate the `request.FILES` property. +If your custom parser needs to support file uploads, you may return a `DataAndFiles` object from the `.parse()` method. `DataAndFiles` should be instantiated with two arguments. The first argument will be used to populate the `request.DATA` property, and the second argument will be used to populate the `request.FILES` property. For example: @@ -133,7 +133,7 @@ For example: A naive raw file upload parser. """ - def parse_stream(self, stream, parser_context): + def parse(self, stream, parser_context): content = stream.read() name = 'example.dat' content_type = 'application/octet-stream' diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index e21433ba..5b58f293 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -134,12 +134,15 @@ We've now got a few comment instances to play with. Let's take a look at serial At this point we've translated the model instance into python native datatypes. To finalise the serialization process we render the data into `json`. - stream = JSONRenderer().render(serializer.data) - stream + content = JSONRenderer().render(serializer.data) + content # '{"id": 1, "email": "leila@example.com", "content": "nothing to say", "created": "2012-08-22T16:20:09.822"}' Deserialization is similar. First we parse a stream into python native datatypes... + import StringIO + + stream = StringIO.StringIO(content) data = JSONParser().parse(stream) ...then we restore those native datatypes into to a fully populated object instance. -- cgit v1.2.3 From 4231995fbd80e45991975ab81d9e570a9f4b72d0 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 17 Oct 2012 22:19:59 +0100 Subject: parser_context includes `view`, `request`, `args`, `kwargs`. (Not `meta` and `upload_handlers`) Consistency with renderer API. --- docs/api-guide/parsers.md | 4 +++- docs/api-guide/renderers.md | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index a950c0e0..70abad9b 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -103,7 +103,9 @@ A stream-like object representing the body of the request. ### parser_context -If supplied, this argument will be a dictionary containing any additional context that may be required to parse the request content. By default it includes the keys `'upload_handlers'` and `'meta'`, which contain the values of the `request.upload_handlers` and `request.meta` properties. +Optional. If supplied, this argument will be a dictionary containing any additional context that may be required to parse the request content. + +By default this will include the following keys: `view`, `request`, `args`, `kwargs`. ## Example diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index c8addb32..24cca181 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -162,11 +162,14 @@ The request data, as set by the `Response()` instantiation. ### `media_type=None` -Optional. If provided, this is the accepted media type, as determined by the content negotiation stage. Depending on the client's `Accept:` header, this may be more specific than the renderer's `media_type` attribute, and may include media type parameters. For example `"application/json; nested=true"`. +Optional. If provided, this is the accepted media type, as determined by the content negotiation stage. + +Depending on the client's `Accept:` header, this may be more specific than the renderer's `media_type` attribute, and may include media type parameters. For example `"application/json; nested=true"`. ### `renderer_context=None` Optional. If provided, this is a dictionary of contextual information provided by the view. + By default this will include the following keys: `view`, `request`, `response`, `args`, `kwargs`. ## Example -- cgit v1.2.3 From fb56f215ae50da0aebe99e05036ece259fd3e6f1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 17 Oct 2012 22:39:07 +0100 Subject: Added `media_type` to `.parse()` - Consistency with renderer API. --- docs/api-guide/parsers.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index 70abad9b..18a5872c 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -101,6 +101,12 @@ The arguments passed to `.parse()` are: A stream-like object representing the body of the request. +### media_type + +Optional. If provided, this is the media type of the incoming request. + +Depending on the request's `Content-Type:` header, this may be more specific than the renderer's `media_type` attribute, and may include media type parameters. For example `"text/plain; charset=utf-8"`. + ### parser_context Optional. If supplied, this argument will be a dictionary containing any additional context that may be required to parse the request content. @@ -118,7 +124,7 @@ The following is an example plaintext parser that will populate the `request.DAT media_type = 'text/plain' - def parse(self, stream, parser_context=None): + def parse(self, stream, media_type=None, parser_context=None): """ Simply return a string representing the body of the request. """ @@ -135,7 +141,7 @@ For example: A naive raw file upload parser. """ - def parse(self, stream, parser_context): + def parse(self, stream, media_type=None, parser_context=None): content = stream.read() name = 'example.dat' content_type = 'application/octet-stream' -- cgit v1.2.3 From e126b615420fed12af58675cb4bb52e749b006bd Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 17 Oct 2012 22:58:18 +0100 Subject: Negotiation API finalized. .select_renderers and .select_parsers --- docs/api-guide/content-negotiation.md | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'docs') diff --git a/docs/api-guide/content-negotiation.md b/docs/api-guide/content-negotiation.md index ad98de3b..b95091c5 100644 --- a/docs/api-guide/content-negotiation.md +++ b/docs/api-guide/content-negotiation.md @@ -7,3 +7,9 @@ > — [RFC 2616][cite], Fielding et al. [cite]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html + +**TODO**: Describe content negotiation style used by REST framework. + +## Custom content negotiation + +It's unlikley that you'll want to provide a custom content negotiation scheme for REST framework, but you can do so if needed. To implement a custom content negotiation scheme, override `BaseContentNegotiation`, and implement the `.select_parser(request, parsers)` and `.select_renderer(request, renderers, format_suffix)` \ No newline at end of file -- cgit v1.2.3 From fed235dd0135c3eb98bb218a51f01ace5ddd3782 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 17 Oct 2012 23:09:11 +0100 Subject: Make settings consistent with corrosponding view attributes --- docs/api-guide/authentication.md | 4 ++-- docs/api-guide/parsers.md | 4 ++-- docs/api-guide/permissions.md | 4 ++-- docs/api-guide/renderers.md | 4 ++-- docs/api-guide/requests.md | 4 ++-- docs/api-guide/settings.md | 20 ++++++++++---------- docs/api-guide/throttling.md | 8 ++++---- docs/css/default.css | 4 ++++ docs/tutorial/quickstart.md | 4 ++-- 9 files changed, 30 insertions(+), 26 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 71f48163..5e5ee4ed 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -26,10 +26,10 @@ The value of `request.user` and `request.auth` for unauthenticated requests can ## Setting the authentication policy -The default authentication policy may be set globally, using the `DEFAULT_AUTHENTICATION` setting. For example. +The default authentication policy may be set globally, using the `DEFAULT_AUTHENTICATION_CLASSES` setting. For example. REST_FRAMEWORK = { - 'DEFAULT_AUTHENTICATION': ( + 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.UserBasicAuthentication', 'rest_framework.authentication.SessionAuthentication', ) diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index 18a5872c..f7a62d3d 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -16,10 +16,10 @@ The set of valid parsers for a view is always defined as a list of classes. Whe ## Setting the parsers -The default set of parsers may be set globally, using the `DEFAULT_PARSERS` 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 requests with `YAML` content. REST_FRAMEWORK = { - 'DEFAULT_PARSERS': ( + 'DEFAULT_PARSER_CLASSES': ( 'rest_framework.parsers.YAMLParser', ) } diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index b25b52be..249f3938 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -25,10 +25,10 @@ Object level permissions are run by REST framework's generic views when `.get_ob ## Setting the permission policy -The default permission policy may be set globally, using the `DEFAULT_PERMISSIONS` setting. For example. +The default permission policy may be set globally, using the `DEFAULT_PERMISSION_CLASSES` setting. For example. REST_FRAMEWORK = { - 'DEFAULT_PERMISSIONS': ( + 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', ) } diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index 24cca181..b3b8d5bc 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -18,10 +18,10 @@ For more information see the documentation on [content negotation][conneg]. ## Setting the renderers -The default set of renderers may be set globally, using the `DEFAULT_RENDERERS` 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 `YAML` as the main media type and also include the self describing API. REST_FRAMEWORK = { - 'DEFAULT_RENDERERS': ( + 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.YAMLRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', ) diff --git a/docs/api-guide/requests.md b/docs/api-guide/requests.md index 439c97bc..2770c6dd 100644 --- a/docs/api-guide/requests.md +++ b/docs/api-guide/requests.md @@ -37,7 +37,7 @@ For clarity inside your code, we recommend using `request.QUERY_PARAMS` instead ## .parsers -The `APIView` class or `@api_view` decorator will ensure that this property is automatically to a list of `Parser` instances, based on the `parser_classes` set on the view or based on the `DEFAULT_PARSERS` setting. +The `APIView` class or `@api_view` decorator will ensure that this property is automatically to a list of `Parser` instances, based on the `parser_classes` set on the view or based on the `DEFAULT_PARSER_CLASSES` setting. You won't typically need to access this property. @@ -125,4 +125,4 @@ Note that due to implementation reasons the `Request` class does not inherit fro [cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion [parsers documentation]: parsers.md [authentication documentation]: authentication.md -[browser enhancements documentation]: ../topics/browser-enhancements.md \ No newline at end of file +[browser enhancements documentation]: ../topics/browser-enhancements.md diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 84acd797..21efc853 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -11,10 +11,10 @@ Configuration for REST framework is all namespaced inside a single Django settin For example your project's `settings.py` file might include something like this: REST_FRAMEWORK = { - 'DEFAULT_RENDERERS': ( + 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.YAMLRenderer', ) - 'DEFAULT_PARSERS': ( + 'DEFAULT_PARSER_CLASSES': ( 'rest_framework.parsers.YAMLParser', ) } @@ -26,7 +26,7 @@ you should use the `api_settings` object. For example. from rest_framework.settings import api_settings - print api_settings.DEFAULT_AUTHENTICATION + print api_settings.DEFAULT_AUTHENTICATION_CLASSES The `api_settings` object will check for any user-defined settings, and otherwise fallback to the default values. Any setting that uses string import paths to refer to a class will automatically import and return the referenced class, instead of the string literal. @@ -34,7 +34,7 @@ The `api_settings` object will check for any user-defined settings, and otherwis # API Reference -## DEFAULT_RENDERERS +## DEFAULT_RENDERER_CLASSES A list or tuple of renderer classes, that determines the default set of renderers that may be used when returning a `Response` object. @@ -46,7 +46,7 @@ Default: 'rest_framework.renderers.TemplateHTMLRenderer' ) -## DEFAULT_PARSERS +## DEFAULT_PARSER_CLASSES A list or tuple of parser classes, that determines the default set of parsers used when accessing the `request.DATA` property. @@ -57,7 +57,7 @@ Default: 'rest_framework.parsers.FormParser' ) -## DEFAULT_AUTHENTICATION +## DEFAULT_AUTHENTICATION_CLASSES A list or tuple of authentication classes, that determines the default set of authenticators used when accessing the `request.user` or `request.auth` properties. @@ -68,25 +68,25 @@ Default: 'rest_framework.authentication.UserBasicAuthentication' ) -## DEFAULT_PERMISSIONS +## DEFAULT_PERMISSION_CLASSES A list or tuple of permission classes, that determines the default set of permissions checked at the start of a view. Default: `()` -## DEFAULT_THROTTLES +## DEFAULT_THROTTLE_CLASSES A list or tuple of throttle classes, that determines the default set of throttles checked at the start of a view. Default: `()` -## DEFAULT_MODEL_SERIALIZER +## DEFAULT_MODEL_SERIALIZER_CLASS **TODO** Default: `rest_framework.serializers.ModelSerializer` -## DEFAULT_PAGINATION_SERIALIZER +## DEFAULT_PAGINATION_SERIALIZER_CLASS **TODO** diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index 22e34187..435b20ce 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -27,10 +27,10 @@ If any throttle check fails an `exceptions.Throttled` exception will be raised, ## Setting the throttling policy -The default throttling policy may be set globally, using the `DEFAULT_THROTTLES` and `DEFAULT_THROTTLE_RATES` settings. For example. +The default throttling policy may be set globally, using the `DEFAULT_THROTTLE_CLASSES` and `DEFAULT_THROTTLE_RATES` settings. For example. REST_FRAMEWORK = { - 'DEFAULT_THROTTLES': ( + 'DEFAULT_THROTTLE_CLASSES': ( 'rest_framework.throttles.AnonThrottle', 'rest_framework.throttles.UserThrottle', ) @@ -100,7 +100,7 @@ For example, multiple user throttle rates could be implemented by using the foll ...and the following settings. REST_FRAMEWORK = { - 'DEFAULT_THROTTLES': ( + 'DEFAULT_THROTTLE_CLASSES': ( 'example.throttles.BurstRateThrottle', 'example.throttles.SustainedRateThrottle', ) @@ -135,7 +135,7 @@ For example, given the following views... ...and the following settings. REST_FRAMEWORK = { - 'DEFAULT_THROTTLES': ( + 'DEFAULT_THROTTLE_CLASSES': ( 'rest_framework.throttles.ScopedRateThrottle', ) 'DEFAULT_THROTTLE_RATES': { diff --git a/docs/css/default.css b/docs/css/default.css index c1d2e885..57446ff9 100644 --- a/docs/css/default.css +++ b/docs/css/default.css @@ -88,6 +88,10 @@ pre { font-weight: bold; } +.nav-list a { + overflow: hidden; +} + /* Set the table of contents to static so it flows back into the content when viewed on tablets and smaller. */ @media (max-width: 767px) { diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 851db3c7..6bde725b 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -126,7 +126,7 @@ We'd also like to set a few global settings. We'd like to turn on pagination, a ) REST_FRAMEWORK = { - 'DEFAULT_PERMISSIONS': ('rest_framework.permissions.IsAdminUser',), + 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',), 'PAGINATE_BY': 10 } @@ -169,4 +169,4 @@ If you want to get a more in depth understanding of how REST framework fits toge [image]: ../img/quickstart.png [tutorial]: 1-serialization.md -[guide]: ../#api-guide \ No newline at end of file +[guide]: ../#api-guide -- cgit v1.2.3 From e8f542aac88677cd95c473d56511cadbc0c67813 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 18 Oct 2012 09:19:38 +0100 Subject: Minor docs fix --- docs/api-guide/parsers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index 18a5872c..0985b2ca 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -91,7 +91,7 @@ You will typically want to use both `FormParser` and `MultiPartParser` together # Custom parsers -To implement a custom parser, you should override `BaseParser`, set the `.media_type` property, and implement the `.parse(self, stream, parser_context)` method. +To implement a custom parser, you should override `BaseParser`, set the `.media_type` property, and implement the `.parse(self, stream, media_type, parser_context)` method. The method should return the data that will be used to populate the `request.DATA` property. @@ -103,7 +103,7 @@ A stream-like object representing the body of the request. ### media_type -Optional. If provided, this is the media type of the incoming request. +Optional. If provided, this is the media type of the incoming request content. Depending on the request's `Content-Type:` header, this may be more specific than the renderer's `media_type` attribute, and may include media type parameters. For example `"text/plain; charset=utf-8"`. -- cgit v1.2.3 From efabd2bb1b762fbdee2b48fa3a6ccb8f23c7e8dc Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 19 Oct 2012 19:59:46 +0100 Subject: docs, docs, docs, docs, docs, docs, docs --- docs/api-guide/content-negotiation.md | 57 +++++++++++++++++++++++++++++++++-- docs/api-guide/format-suffixes.md | 52 +++++++++++++++++++++++++++++++- docs/api-guide/pagination.md | 6 +++- 3 files changed, 110 insertions(+), 5 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/content-negotiation.md b/docs/api-guide/content-negotiation.md index b95091c5..78dc0747 100644 --- a/docs/api-guide/content-negotiation.md +++ b/docs/api-guide/content-negotiation.md @@ -8,8 +8,59 @@ [cite]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html -**TODO**: Describe content negotiation style used by REST framework. +Content negotiation is the process of selecting one of multiple possible representations to return to a client, based on client or server preferences. -## Custom content negotiation +## Determining the accepted renderer -It's unlikley that you'll want to provide a custom content negotiation scheme for REST framework, but you can do so if needed. To implement a custom content negotiation scheme, override `BaseContentNegotiation`, and implement the `.select_parser(request, parsers)` and `.select_renderer(request, renderers, format_suffix)` \ No newline at end of file +REST framework uses a simple style of content negotition to determine which media type should be returned to a client, based on the available renderers, the priorities of each of those renderers, and the client's `Accept:` header. The style used is partly client-driven, and partly server-driven. + +1. More specific media types are given preference to less specific media types. +2. If multiple media types have the same specificity, then preference is given to based on the ordering of the renderers configured for the given view. + +For example, given the following `Accept` header: + + application/json; indent=4, application/json, application/yaml, text/html, */* + +The priorities for each of the given media types would be: + +* `application/json; indent=4` +* `application/json`, `application/yaml` and `text/html` +* `*/*` + +If the requested view was only configured with renderers for `YAML` and `HTML`, then REST framework would select whichever renderer was listed first in the `renderer_classes` list or `DEFAULT_RENDERER_CLASSES` setting. + +For more information on the `HTTP Accept` header, see [RFC 2616][accept-header] + +--- + +**Note**: "q" values are not taken into account by REST framework when determining preference. The use of "q" values negatively impacts caching, and in the author's opinion they are an unnecessary and overcomplicated approach to content negotiation. + +This is a valid approach as the HTTP spec deliberately underspecifies how a server should weight server-based preferences against client-based preferences. + +--- + +# Custom content negotiation + +It's unlikley that you'll want to provide a custom content negotiation scheme for REST framework, but you can do so if needed. To implement a custom content negotiation scheme override `BaseContentNegotiation`. + +REST framework's content negotiation classes handle selection of both the approprate parser for the requesr, and the appropriate renderer for the response, so you should implement both the `.select_parser(request, parsers)` and `.select_renderer(request, renderers, format_suffix)` methods. + +## Example + +The following is a custom content negotiation class which ignores the client +request when selecting the appropriate parser or renderer. + + class IgnoreClientContentNegotiation(BaseContentNegotiation): + def select_parser(self, request, parsers): + """ + Select the first parser in the `.parser_classes` list. + """ + return parsers[0] + + def select_renderer(self, request, renderers, format_suffix): + """ + Select the first renderer in the `.renderer_classes` list. + """ + return renderers[0] + +[accept-header]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html \ No newline at end of file diff --git a/docs/api-guide/format-suffixes.md b/docs/api-guide/format-suffixes.md index 7d72d9f8..6d5feba4 100644 --- a/docs/api-guide/format-suffixes.md +++ b/docs/api-guide/format-suffixes.md @@ -7,5 +7,55 @@ used all the time. > > — Roy Fielding, [REST discuss mailing list][cite] -[cite]: http://tech.groups.yahoo.com/group/rest-discuss/message/5857 +A common pattern for Web APIs is to use filename extensions on URLs to provide an endpoint for a given media type. For example, 'http://example.com/api/users.json' to serve a JSON representation. + +Adding format-suffix patterns to each individual entry in the URLconf for your API is error-prone and non-DRY, so REST framework provides a shortcut to adding these patterns to your URLConf. + +## format_suffix_patterns + +**Signature**: format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None) + +Returns a URL pattern list which includes format suffix patterns appended to each of the URL patterns provided. + +Arguments: + +* **urlpatterns**: Required. A URL pattern list. +* **suffix_required**: Optional. A boolean indicating if suffixes in the URLs should be optional or mandatory. Defaults to `False`, meaning that suffixes are optional by default. +* **allowed**: Optional. A list or tuple of valid format suffixes. If not provided, a wildcard format suffix pattern will be used. + +Example: + + from rest_framework.urlpatterns import format_suffix_patterns + + urlpatterns = patterns('blog.views', + url(r'^/$', 'api_root'), + url(r'^comment/$', 'comment_root'), + url(r'^comment/(?P[0-9]+)/$', 'comment_instance') + ) + + urlpatterns = format_suffix_patterns(urlpatterns, allowed=['json', 'html']) + +When using `format_suffix_patterns`, you must make sure to add the `'format'` keyword argument to the corresponding views. For example. + @api_view(('GET',)) + def api_root(request, format=None): + # do stuff... + +The name of the kwarg used may be modified by using the `FORMAT_SUFFIX_KWARG` setting. + +Also note that `format_suffix_patterns` does not support descending into `include` URL patterns. + +--- + +## Accept headers vs. format suffixes + +There seems to be a view among some of the Web community that filename extensions are not a RESTful pattern, and that `HTTP Accept` headers should always be used instead. + +It is actually a misconception. For example, take the following quote from Roy Fielding discussing the relative merits of query parameter media-type indicators vs. file extension media-type indicators: + +“That's why I always prefer extensions. Neither choice has anything to do with REST.” — Roy Fielding, [REST discuss mailing list][cite2] + +The quote does not mention Accept headers, but it does make it clear that format suffixes should be considered an acceptable pattern. + +[cite]: http://tech.groups.yahoo.com/group/rest-discuss/message/5857 +[cite2]: http://tech.groups.yahoo.com/group/rest-discuss/message/14844 \ No newline at end of file diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index e416de02..597baba4 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -100,12 +100,16 @@ You can also set the pagination style on a per-view basis, using the `ListAPIVie 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. -## Custom pagination serializers +--- + +# Custom pagination serializers To create a custom pagination serializer class you should override `pagination.BasePaginationSerializer` and set the fields that you want the serializer to return. You can also override the name used for the object list field, by setting the `results_field` attribute, which defaults to `'results'`. +## 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. class LinksSerializer(serializers.Serializer): -- cgit v1.2.3 From 71a93930fd4df7a1f5f92c67633b813a26a5e938 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Sun, 21 Oct 2012 16:34:07 +0200 Subject: Fixing spelling errors. --- docs/api-guide/authentication.md | 2 +- docs/api-guide/content-negotiation.md | 8 ++++---- docs/api-guide/exceptions.md | 4 ++-- docs/api-guide/fields.md | 8 ++++---- docs/api-guide/parsers.md | 4 ++-- docs/api-guide/permissions.md | 2 +- docs/api-guide/renderers.md | 4 ++-- docs/api-guide/requests.md | 12 ++++++------ docs/api-guide/responses.md | 2 +- docs/api-guide/reverse.md | 2 +- docs/api-guide/serializers.md | 4 ++-- docs/api-guide/throttling.md | 6 +++--- docs/index.md | 4 ++-- docs/topics/csrf.md | 4 ++-- docs/topics/rest-hypermedia-hateoas.md | 6 +++--- docs/tutorial/2-requests-and-responses.md | 2 +- docs/tutorial/3-class-based-views.md | 2 +- docs/tutorial/6-resource-orientated-projects.md | 2 +- 18 files changed, 39 insertions(+), 39 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 5e5ee4ed..7bad4867 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -86,7 +86,7 @@ You'll also need to create tokens for your users. token = Token.objects.create(user=...) print token.key -For clients to authenticate, the token key should be included in the `Authorization` HTTP header. The key should be prefixed by the string literal "Token", with whitespace seperating the two strings. For example: +For clients to authenticate, the token key should be included in the `Authorization` HTTP header. The key should be prefixed by the string literal "Token", with whitespace separating the two strings. For example: Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b diff --git a/docs/api-guide/content-negotiation.md b/docs/api-guide/content-negotiation.md index 78dc0747..10288c94 100644 --- a/docs/api-guide/content-negotiation.md +++ b/docs/api-guide/content-negotiation.md @@ -12,7 +12,7 @@ Content negotiation is the process of selecting one of multiple possible represe ## Determining the accepted renderer -REST framework uses a simple style of content negotition to determine which media type should be returned to a client, based on the available renderers, the priorities of each of those renderers, and the client's `Accept:` header. The style used is partly client-driven, and partly server-driven. +REST framework uses a simple style of content negotiation to determine which media type should be returned to a client, based on the available renderers, the priorities of each of those renderers, and the client's `Accept:` header. The style used is partly client-driven, and partly server-driven. 1. More specific media types are given preference to less specific media types. 2. If multiple media types have the same specificity, then preference is given to based on the ordering of the renderers configured for the given view. @@ -41,9 +41,9 @@ This is a valid approach as the HTTP spec deliberately underspecifies how a serv # Custom content negotiation -It's unlikley that you'll want to provide a custom content negotiation scheme for REST framework, but you can do so if needed. To implement a custom content negotiation scheme override `BaseContentNegotiation`. +It's unlikely that you'll want to provide a custom content negotiation scheme for REST framework, but you can do so if needed. To implement a custom content negotiation scheme override `BaseContentNegotiation`. -REST framework's content negotiation classes handle selection of both the approprate parser for the requesr, and the appropriate renderer for the response, so you should implement both the `.select_parser(request, parsers)` and `.select_renderer(request, renderers, format_suffix)` methods. +REST framework's content negotiation classes handle selection of both the appropriate parser for the request, and the appropriate renderer for the response, so you should implement both the `.select_parser(request, parsers)` and `.select_renderer(request, renderers, format_suffix)` methods. ## Example @@ -63,4 +63,4 @@ request when selecting the appropriate parser or renderer. """ return renderers[0] -[accept-header]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html \ No newline at end of file +[accept-header]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index 33cf1ca8..ba57fde8 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -25,7 +25,7 @@ For example, the following request: DELETE http://api.example.com/foo/bar HTTP/1.1 Accept: application/json -Might recieve an error response indicating that the `DELETE` method is not allowed on that resource: +Might receive an error response indicating that the `DELETE` method is not allowed on that resource: HTTP/1.1 405 Method Not Allowed Content-Type: application/json; charset=utf-8 @@ -85,4 +85,4 @@ Raised when an incoming request fails the throttling checks. By default this exception results in a response with the HTTP status code "429 Too Many Requests". -[cite]: http://www.doughellmann.com/articles/how-tos/python-exception-handling/index.html \ No newline at end of file +[cite]: http://www.doughellmann.com/articles/how-tos/python-exception-handling/index.html diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index dc9ab045..b3cf186c 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -42,7 +42,7 @@ A serializer definition that looked like this: class Meta: fields = ('url', 'owner', 'name', 'expired') -Would produced output similar to: +Would produce output similar to: { 'url': 'http://example.com/api/accounts/3/', @@ -51,7 +51,7 @@ Would produced output similar to: 'expired': True } -Be default, the `Field` class will perform a basic translation of the source value into primative datatypes, falling back to unicode representations of complex datatypes when neccesary. +By default, the `Field` class will perform a basic translation of the source value into primative datatypes, falling back to unicode representations of complex datatypes when necessary. You can customize this behaviour by overriding the `.to_native(self, value)` method. @@ -84,7 +84,7 @@ or `django.db.models.fields.TextField`. ## EmailField -A text representation, validates the text to be a valid e-mail adress. Corresponds to `django.db.models.fields.EmailField` +A text representation, validates the text to be a valid e-mail address. Corresponds to `django.db.models.fields.EmailField` ## DateField @@ -165,7 +165,7 @@ And a model serializer defined like this: model = Bookmark exclude = ('id',) -The an example output format for a Bookmark instance would be: +Then an example output format for a Bookmark instance would be: { 'tags': [u'django', u'python'], diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index c35dfd05..ac904720 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -8,7 +8,7 @@ sending more complex data than simple forms > > — Malcom Tredinnick, [Django developers group][cite] -REST framework includes a number of built in Parser classes, that allow you to accept requests with various media types. There is also support for defining your own custom parsers, which gives you the flexiblity to design the media types that your API accepts. +REST framework includes a number of built in Parser classes, that allow you to accept requests with various media types. There is also support for defining your own custom parsers, which gives you the flexibility to design the media types that your API accepts. ## How the parser is determined @@ -65,7 +65,7 @@ Parses `YAML` request content. Parses REST framework's default style of `XML` request 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`. +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. diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 249f3938..0b7b32e9 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -6,7 +6,7 @@ > > — [Apple Developer Documentation][cite] -Together with [authentication] and [throttling], permissions determine wheter a request should be granted or denied access. +Together with [authentication] and [throttling], permissions determine whether a request should be granted or denied access. Permission checks are always run at the very start of the view, before any other code is allowed to proceed. Permission checks will typically use the authentication information in the `request.user` and `request.auth` properties to determine if the incoming request should be permitted. diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index b3b8d5bc..b6db376c 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -6,7 +6,7 @@ > > — [Django documentation][cite] -REST framework includes a number of built in Renderer classes, that allow you to return responses with various media types. There is also support for defining your own custom renderers, which gives you the flexiblity to design your own media types. +REST framework includes a number of built in Renderer classes, that allow you to return responses with various media types. There is also support for defining your own custom renderers, which gives you the flexibility to design your own media types. ## How the renderer is determined @@ -229,7 +229,7 @@ For example: ## Designing your media types -For the purposes of many Web APIs, simple `JSON` responses with hyperlinked relations may be sufficient. If you want to fully embrace RESTful design and [HATEOAS] you'll neeed to consider the design and usage of your media types in more detail. +For the purposes of many Web APIs, simple `JSON` responses with hyperlinked relations may be sufficient. If you want to fully embrace RESTful design and [HATEOAS] you'll need to consider the design and usage of your media types in more detail. In [the words of Roy Fielding][quote], "A REST API should spend almost all of its descriptive effort in defining the media type(s) used for representing resources and driving application state, or in defining extended relation names and/or hypertext-enabled mark-up for existing standard media types.". diff --git a/docs/api-guide/requests.md b/docs/api-guide/requests.md index 2770c6dd..72932f5d 100644 --- a/docs/api-guide/requests.md +++ b/docs/api-guide/requests.md @@ -25,19 +25,19 @@ For more details see the [parsers documentation]. ## .FILES -`request.FILES` returns any uploaded files that may be present in the content of the request body. This is the same as the standard `HttpRequest` behavior, except that the same flexible request parsing that is used for `request.DATA`. +`request.FILES` returns any uploaded files that may be present in the content of the request body. This is the same as the standard `HttpRequest` behavior, except that the same flexible request parsing is used for `request.DATA`. For more details see the [parsers documentation]. ## .QUERY_PARAMS -`request.QUERY_PARAMS` is a more correcly named synonym for `request.GET`. +`request.QUERY_PARAMS` is a more correctly named synonym for `request.GET`. For clarity inside your code, we recommend using `request.QUERY_PARAMS` instead of the usual `request.GET`, as *any* HTTP method type may include query parameters. ## .parsers -The `APIView` class or `@api_view` decorator will ensure that this property is automatically to a list of `Parser` instances, based on the `parser_classes` set on the view or based on the `DEFAULT_PARSER_CLASSES` setting. +The `APIView` class or `@api_view` decorator will ensure that this property is automatically set to a list of `Parser` instances, based on the `parser_classes` set on the view or based on the `DEFAULT_PARSER_CLASSES` setting. You won't typically need to access this property. @@ -51,7 +51,7 @@ If a client sends a request with a content-type that cannot be parsed then a `Un # Authentication -REST framework provides flexbile, per-request authentication, that gives you the abilty to: +REST framework provides flexible, per-request authentication, that gives you the ability to: * Use different authentication policies for different parts of your API. * Support the use of multiple authentication policies. @@ -75,7 +75,7 @@ For more details see the [authentication documentation]. ## .authenticators -The `APIView` class or `@api_view` decorator will ensure that this property is automatically to a list of `Authentication` instances, based on the `authentication_classes` set on the view or based on the `DEFAULT_AUTHENTICATORS` setting. +The `APIView` class or `@api_view` decorator will ensure that this property is automatically set to a list of `Authentication` instances, based on the `authentication_classes` set on the view or based on the `DEFAULT_AUTHENTICATORS` setting. You won't typically need to access this property. @@ -83,7 +83,7 @@ You won't typically need to access this property. # Browser enhancements -REST framework supports a few browser enhancments such as browser-based `PUT` and `DELETE` forms. +REST framework supports a few browser enhancements such as browser-based `PUT` and `DELETE` forms. ## .method diff --git a/docs/api-guide/responses.md b/docs/api-guide/responses.md index 395decda..794f9377 100644 --- a/docs/api-guide/responses.md +++ b/docs/api-guide/responses.md @@ -86,7 +86,7 @@ The `Response` class extends `SimpleTemplateResponse`, and all the usual attribu **Signature:** `.render()` -As with any other `TemplateResponse`, this methd is called to render the serialized data of the response into the final response content. When `.render()` is called, the response content will be set to the result of calling the `.render(data, accepted_media_type, renderer_context)` method on the `accepted_renderer` instance. +As with any other `TemplateResponse`, this method is called to render the serialized data of the response into the final response content. When `.render()` is called, the response content will be set to the result of calling the `.render(data, accepted_media_type, renderer_context)` method on the `accepted_renderer` instance. You won't typically need to call `.render()` yourself, as it's handled by Django's standard response cycle. diff --git a/docs/api-guide/reverse.md b/docs/api-guide/reverse.md index 12346eb4..19930dc3 100644 --- a/docs/api-guide/reverse.md +++ b/docs/api-guide/reverse.md @@ -6,7 +6,7 @@ > > — Roy Fielding, [Architectural Styles and the Design of Network-based Software Architectures][cite] -As a rule, it's probably better practice to return absolute URIs from you Web APIs, such as `http://example.com/foobar`, rather than returning relative URIs, such as `/foobar`. +As a rule, it's probably better practice to return absolute URIs from your Web APIs, such as `http://example.com/foobar`, rather than returning relative URIs, such as `/foobar`. The advantages of doing so are: diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 47958fe3..c10a3f44 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -175,7 +175,7 @@ You can add extra fields to a `ModelSerializer` or override the default fields b class Meta: model = Account -Extra fields can corrospond to any property or callable on the model. +Extra fields can correspond to any property or callable on the model. ## Relational fields @@ -187,7 +187,7 @@ The `PrimaryKeyRelatedField` and `HyperlinkedRelatedField` fields provide altern The `ModelSerializer` class can itself be used as a field, in order to serialize relationships using nested representations. -The `RelatedField` class may be subclassed to create a custom represenation of a relationship. The subclass should override `.to_native()`, and optionally `.from_native()` if deserialization is supported. +The `RelatedField` class may be subclassed to create a custom representation of a relationship. The subclass should override `.to_native()`, and optionally `.from_native()` if deserialization is supported. All the relational fields may be used for any relationship or reverse relationship on a model. diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index 435b20ce..d54433b1 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -80,7 +80,7 @@ The allowed request rate is determined from one of the following (in order of pr ## UserRateThrottle -The `UserThrottle` will throttle users to a given rate of requests across the API. The user id is used to generate a unique key to throttle against. Unauthenticted requests will fall back to using the IP address of the incoming request to generate a unique key to throttle against. +The `UserThrottle` will throttle users to a given rate of requests across the API. The user id is used to generate a unique key to throttle against. Unauthenticated requests will fall back to using the IP address of the incoming request to generate a unique key to throttle against. The allowed request rate is determined from one of the following (in order of preference). @@ -114,7 +114,7 @@ For example, multiple user throttle rates could be implemented by using the foll ## ScopedRateThrottle -The `ScopedThrottle` class can be used to restrict access to specific parts of the API. This throttle will only be applied if the view that is being accessed includes a `.throttle_scope` property. The unique throttle key will then be formed by concatenating the "scope" of the request with the unqiue user id or IP address. +The `ScopedThrottle` class can be used to restrict access to specific parts of the API. This throttle will only be applied if the view that is being accessed includes a `.throttle_scope` property. The unique throttle key will then be formed by concatenating the "scope" of the request with the unique user id or IP address. The allowed request rate is determined by the `DEFAULT_THROTTLE_RATES` setting using a key from the request "scope". @@ -152,6 +152,6 @@ User requests to either `ContactListView` or `ContactDetailView` would be restri To create a custom throttle, override `BaseThrottle` and implement `.allow_request(request, view)`. The method should return `True` if the request should be allowed, and `False` otherwise. -Optionally you may also override the `.wait()` method. If implemented, `.wait()` should return a recomended number of seconds to wait before attempting the next request, or `None`. The `.wait()` method will only be called if `.allow_request()` has previously returned `False`. +Optionally you may also override the `.wait()` method. If implemented, `.wait()` should return a recommended number of seconds to wait before attempting the next request, or `None`. The `.wait()` method will only be called if `.allow_request()` has previously returned `False`. [permissions]: permissions.md diff --git a/docs/index.md b/docs/index.md index 02bda081..f66ba7f4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -47,7 +47,7 @@ Add `rest_framework` to your `INSTALLED_APPS`. 'rest_framework', ) -If you're intending to use the browserable API you'll want to add REST framework's login and logout views. Add the following to your root `urls.py` file. +If you're intending to use the browseable API you'll want to add REST framework's login and logout views. Add the following to your root `urls.py` file. urlpatterns = patterns('', ... @@ -195,4 +195,4 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework [DabApps]: http://dabapps.com -[email]: mailto:tom@tomchristie.com \ No newline at end of file +[email]: mailto:tom@tomchristie.com diff --git a/docs/topics/csrf.md b/docs/topics/csrf.md index a2ee1b9c..043144c1 100644 --- a/docs/topics/csrf.md +++ b/docs/topics/csrf.md @@ -5,8 +5,8 @@ > — [Jeff Atwood][cite] * Explain need to add CSRF token to AJAX requests. -* Explain defered CSRF style used by REST framework +* Explain deferred CSRF style used by REST framework * Why you should use Django's standard login/logout views, and not REST framework view -[cite]: http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html \ No newline at end of file +[cite]: http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html diff --git a/docs/topics/rest-hypermedia-hateoas.md b/docs/topics/rest-hypermedia-hateoas.md index 8b0309b9..d7646892 100644 --- a/docs/topics/rest-hypermedia-hateoas.md +++ b/docs/topics/rest-hypermedia-hateoas.md @@ -4,7 +4,7 @@ > > — Mike Amundsen, [REST fest 2012 keynote][cite]. -First off, the disclaimer. The name "Django REST framework" was choosen simply to sure the project would be easily found by developers. Throughout the documentation we try to use the more simple and technically correct terminology of "Web APIs". +First off, the disclaimer. The name "Django REST framework" was chosen simply to sure the project would be easily found by developers. Throughout the documentation we try to use the more simple and technically correct terminology of "Web APIs". If you are serious about designing a Hypermedia APIs, you should look to resources outside of this documentation to help inform your design choices. @@ -22,7 +22,7 @@ For a more thorough background, check out Klabnik's [Hypermedia API reading list ## Building Hypermedia APIs with REST framework -REST framework is an agnositic Web API toolkit. It does help guide you towards building well-connected APIs, and makes it easy to design appropriate media types, but it does not strictly enforce any particular design style. +REST framework is an agnostic Web API toolkit. It does help guide you towards building well-connected APIs, and makes it easy to design appropriate media types, but it does not strictly enforce any particular design style. ## What REST framework provides. @@ -50,4 +50,4 @@ What REST framework doesn't do is give you is machine readable hypermedia format [parser]: ../api-guide/parsers.md [renderer]: ../api-guide/renderers.md [fields]: ../api-guide/fields.md -[conneg]: ../api-guide/content-negotiation.md \ No newline at end of file +[conneg]: ../api-guide/content-negotiation.md diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md index 7c8fc044..fc37322a 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -5,7 +5,7 @@ Let's introduce a couple of essential building blocks. ## Request objects -REST framework intoduces a `Request` object that extends the regular `HttpRequest`, and provides more flexible request parsing. The core functionality of the `Request` object is the `request.DATA` attribute, which is similar to `request.POST`, but more useful for working with Web APIs. +REST framework introduces a `Request` object that extends the regular `HttpRequest`, and provides more flexible request parsing. The core functionality of the `Request` object is the `request.DATA` attribute, which is similar to `request.POST`, but more useful for working with Web APIs. request.POST # Only handles form data. Only works for 'POST' method. request.DATA # Handles arbitrary data. Works any HTTP request with content. diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md index 2f273364..0ee81ea3 100644 --- a/docs/tutorial/3-class-based-views.md +++ b/docs/tutorial/3-class-based-views.md @@ -107,7 +107,7 @@ Let's take a look at how we can compose our views by using the mixin classes. We'll take a moment to examine exactly what's happening here - We're building our view using `MultipleObjectBaseView`, and adding in `ListModelMixin` and `CreateModelMixin`. -The base class provides the core functionality, and the mixin classes provide the `.list()` and `.create()` actions. We're then explictly binding the `get` and `post` methods to the appropriate actions. Simple enough stuff so far. +The base class provides the core functionality, and the mixin classes provide the `.list()` and `.create()` actions. We're then explicitly binding the `get` and `post` methods to the appropriate actions. Simple enough stuff so far. class CommentInstance(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, diff --git a/docs/tutorial/6-resource-orientated-projects.md b/docs/tutorial/6-resource-orientated-projects.md index e7190a77..9ee599ae 100644 --- a/docs/tutorial/6-resource-orientated-projects.md +++ b/docs/tutorial/6-resource-orientated-projects.md @@ -5,7 +5,7 @@ Resource classes are just View classes that don't have any handler methods bound This allows us to: * Encapsulate common behaviour across a class of views, in a single Resource class. -* Separate out the actions of a Resource from the specfics of how those actions should be bound to a particular set of URLs. +* Separate out the actions of a Resource from the specifics of how those actions should be bound to a particular set of URLs. ## Refactoring to use Resources, not Views -- cgit v1.2.3 From 65d4970bf71d31669f10dc0cecd4a2a00acd7393 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Sun, 21 Oct 2012 16:34:50 +0200 Subject: Changed IsAdmin -> IsAdminUser in example --- docs/api-guide/views.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md index 77349252..e3fbadb2 100644 --- a/docs/api-guide/views.md +++ b/docs/api-guide/views.md @@ -27,7 +27,7 @@ For example: * Only admin users are able to access this view. """ authentication_classes = (authentication.TokenAuthentication,) - permission_classes = (permissions.IsAdmin,) + permission_classes = (permissions.IsAdminUser,) def get(self, request, format=None): """ @@ -123,4 +123,4 @@ REST framework also gives you to work with regular function based views... **[TODO]** [cite]: http://reinout.vanrees.org/weblog/2011/08/24/class-based-views-usage.html -[cite2]: http://www.boredomandlaziness.org/2012/05/djangos-cbvs-are-not-mistake-but.html \ No newline at end of file +[cite2]: http://www.boredomandlaziness.org/2012/05/djangos-cbvs-are-not-mistake-but.html -- cgit v1.2.3 From 13d0a829390105aa53602be7dc713092ead5a66c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sun, 21 Oct 2012 17:40:49 +0100 Subject: Minor docs tweaks --- docs/api-guide/fields.md | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index dc9ab045..234c65ad 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -73,34 +73,52 @@ These fields represent basic datatypes, and support both reading and writing val ## BooleanField -A Boolean representation, corresponds to `django.db.models.fields.BooleanField`. +A Boolean representation. + +Corresponds to `django.db.models.fields.BooleanField`. ## CharField -A text representation, optionally validates the text to be shorter than `max_length` and longer than `min_length`, corresponds to `django.db.models.fields.CharField` +A text representation, optionally validates the text to be shorter than `max_length` and longer than `min_length`. + +Corresponds to `django.db.models.fields.CharField` or `django.db.models.fields.TextField`. -**Signature:** `CharField([max_length=[, min_length=]])` +**Signature:** `CharField(max_length=None, min_length=None)` + +## ChoiceField + +A field that can accept on of a limited set of choices. ## EmailField -A text representation, validates the text to be a valid e-mail adress. Corresponds to `django.db.models.fields.EmailField` +A text representation, validates the text to be a valid e-mail adress. + +Corresponds to `django.db.models.fields.EmailField` ## DateField -A date representation. Corresponds to `django.db.models.fields.DateField` +A date representation. + +Corresponds to `django.db.models.fields.DateField` ## DateTimeField -A date and time representation. Corresponds to `django.db.models.fields.DateTimeField` +A date and time representation. + +Corresponds to `django.db.models.fields.DateTimeField` ## IntegerField -An integer representation. Corresponds to `django.db.models.fields.IntegerField`, `django.db.models.fields.SmallIntegerField`, `django.db.models.fields.PositiveIntegerField` and `django.db.models.fields.PositiveSmallIntegerField` +An integer representation. + +Corresponds to `django.db.models.fields.IntegerField`, `django.db.models.fields.SmallIntegerField`, `django.db.models.fields.PositiveIntegerField` and `django.db.models.fields.PositiveSmallIntegerField` ## FloatField -A floating point representation. Corresponds to `django.db.models.fields.FloatField`. +A floating point representation. + +Corresponds to `django.db.models.fields.FloatField`. --- -- cgit v1.2.3 From aba0172f5c988af145113678fe3d4f411111d4ff Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Mon, 22 Oct 2012 21:31:15 +0300 Subject: Update docs/api-guide/fields.md Fix typo.--- docs/api-guide/fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 189ed76f..7e117df7 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -88,7 +88,7 @@ or `django.db.models.fields.TextField`. ## ChoiceField -A field that can accept on of a limited set of choices. +A field that can accept a value out of a limited set of choices. ## EmailField -- cgit v1.2.3 From 51fae73f3d565e2702c72ff9841cc072d6490804 Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Wed, 24 Oct 2012 09:28:10 +0100 Subject: Implement per-field validation on Serializers --- docs/api-guide/serializers.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'docs') diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index c10a3f44..e1e12e74 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -78,6 +78,23 @@ When deserializing data, you always need to call `is_valid()` before attempting **TODO: Describe validation in more depth** +## Custom field validation + +Like Django forms, you can specify custom field-level validation by adding `clean_()` methods to your `Serializer` subclass. This method takes a dictionary of deserialized data as a first argument, and the field name in that data as a second argument (which will be either the name of the field or the value of the `source` argument, if one was provided.) It should either return the data dictionary or raise a `ValidationError`. For example: + + class BlogPostSerializer(Serializer): + title = serializers.CharField(max_length=100) + content = serializers.CharField() + + def clean_title(self, data, source): + """ + Check that the blog post is about Django + """ + value = data[source] + if "Django" not in value: + raise ValidationError("Blog post is not about Django") + return data + ## Dealing with nested objects The previous example is fine for dealing with objects that only have simple datatypes, but sometimes we also need to be able to represent more complex objects, -- cgit v1.2.3 From 388a807f64f60d84556288e2ade4f0fe57a8e66b Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Wed, 24 Oct 2012 11:27:01 +0100 Subject: Switch from clean_ to validate_, clarify documentation --- docs/api-guide/serializers.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index e1e12e74..9011d31f 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -80,19 +80,21 @@ When deserializing data, you always need to call `is_valid()` before attempting ## Custom field validation -Like Django forms, you can specify custom field-level validation by adding `clean_()` methods to your `Serializer` subclass. This method takes a dictionary of deserialized data as a first argument, and the field name in that data as a second argument (which will be either the name of the field or the value of the `source` argument, if one was provided.) It should either return the data dictionary or raise a `ValidationError`. For example: +You can specify custom field-level validation by adding `validate_()` methods to your `Serializer` subclass. These are analagous to `clean_` methods on Django forms, but accept slightly different arguments. They take a dictionary of deserialized data as a first argument, and the field name in that data as a second argument (which will be either the name of the field or the value of the `source` argument to the field, if one was provided). Your `validate_` methods should either just return the data dictionary or raise a `ValidationError`. For example: - class BlogPostSerializer(Serializer): + from rest_framework import serializers + + class BlogPostSerializer(serializers.Serializer): title = serializers.CharField(max_length=100) content = serializers.CharField() - def clean_title(self, data, source): + def validate_title(self, data, source): """ Check that the blog post is about Django """ value = data[source] if "Django" not in value: - raise ValidationError("Blog post is not about Django") + raise serializers.ValidationError("Blog post is not about Django") return data ## Dealing with nested objects -- cgit v1.2.3 From ac2d39892d6b3fbbe5cd53b9ef83367249ba4880 Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Wed, 24 Oct 2012 11:39:17 +0100 Subject: Add cross-field validate method --- docs/api-guide/serializers.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 9011d31f..40f8a170 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -76,9 +76,7 @@ Deserialization is similar. First we parse a stream into python native datatype When deserializing data, you always need to call `is_valid()` before attempting to access the deserialized object. If any validation errors occur, the `.errors` and `.non_field_errors` properties will contain the resulting error messages. -**TODO: Describe validation in more depth** - -## Custom field validation +### Field-level validation You can specify custom field-level validation by adding `validate_()` methods to your `Serializer` subclass. These are analagous to `clean_` methods on Django forms, but accept slightly different arguments. They take a dictionary of deserialized data as a first argument, and the field name in that data as a second argument (which will be either the name of the field or the value of the `source` argument to the field, if one was provided). Your `validate_` methods should either just return the data dictionary or raise a `ValidationError`. For example: @@ -97,6 +95,10 @@ You can specify custom field-level validation by adding `validate_()` raise serializers.ValidationError("Blog post is not about Django") return data +### Final cross-field validation + +To do any other validation that requires access to multiple fields, add a method called `validate` to your `Serializer` subclass. This method takes a single argument, which is the `attrs` dictionary. It should raise a `ValidationError` if necessary, or just return `attrs`. + ## Dealing with nested objects The previous example is fine for dealing with objects that only have simple datatypes, but sometimes we also need to be able to represent more complex objects, -- cgit v1.2.3 From d60d598e0255fb3d55a1213d1025447d83523658 Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Wed, 24 Oct 2012 11:43:30 +0100 Subject: Clean up internal names and documentation --- docs/api-guide/serializers.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 40f8a170..50505d30 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -78,7 +78,7 @@ When deserializing data, you always need to call `is_valid()` before attempting ### Field-level validation -You can specify custom field-level validation by adding `validate_()` methods to your `Serializer` subclass. These are analagous to `clean_` methods on Django forms, but accept slightly different arguments. They take a dictionary of deserialized data as a first argument, and the field name in that data as a second argument (which will be either the name of the field or the value of the `source` argument to the field, if one was provided). Your `validate_` methods should either just return the data dictionary or raise a `ValidationError`. For example: +You can specify custom field-level validation by adding `validate_()` methods to your `Serializer` subclass. These are analagous to `clean_` methods on Django forms, but accept slightly different arguments. They take a dictionary of deserialized attributes as a first argument, and the field name in that dictionary as a second argument (which will be either the name of the field or the value of the `source` argument to the field, if one was provided). Your `validate_` methods should either just return the attrs dictionary or raise a `ValidationError`. For example: from rest_framework import serializers @@ -86,14 +86,14 @@ You can specify custom field-level validation by adding `validate_()` title = serializers.CharField(max_length=100) content = serializers.CharField() - def validate_title(self, data, source): + def validate_title(self, attrs, source): """ Check that the blog post is about Django """ - value = data[source] + value = attrs[source] if "Django" not in value: raise serializers.ValidationError("Blog post is not about Django") - return data + return attrs ### Final cross-field validation -- cgit v1.2.3 From d97c71212423dd79abafc5aab21afc4eccfc4529 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 24 Oct 2012 20:49:09 +0200 Subject: Fix typo reported by @diviei --- docs/api-guide/serializers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 50505d30..057827d3 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -254,7 +254,7 @@ When serializing objects using a nested representation any occurances of recursi def get_related_field(self, model_field, to_many=False): queryset = model_field.rel.to._default_manager if to_many: - return return serializers.ManyRelatedField(queryset=queryset) + return serializers.ManyRelatedField(queryset=queryset) return serializers.RelatedField(queryset=queryset) def get_field(self, model_field): -- cgit v1.2.3 From 0aed70dc8b9bf2bf6c6bbd540ffadfae9f74bbaa Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 24 Oct 2012 20:50:45 +0200 Subject: Added @diviei - Thanks! --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) (limited to 'docs') diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 6df99237..27a56326 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -48,6 +48,7 @@ The following people have helped make REST framework great. * Max Hurl - [maximilianhurl] * Tomi Pajunen - [eofs] * Rob Dobson - [rdobson] +* Daniel Vaca Araujo - [diviei] Many thanks to everyone who's contributed to the project. @@ -127,3 +128,4 @@ To contact the author directly: [maximilianhurl]: https://github.com/maximilianhurl [eofs]: https://github.com/eofs [rdobson]: https://github.com/rdobson +[diviei]: https://github.com/diviei -- cgit v1.2.3 From 1ceca69e5fa64344f1a039526fb653bf6bbd8a9d Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Thu, 25 Oct 2012 13:50:48 +0100 Subject: Update generic view documentation --- docs/api-guide/generic-views.md | 62 ++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 19 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 8bf7a7e2..7ca0f905 100644 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -49,21 +49,21 @@ For very simple cases you might want to pass through any class attributes using The following classes are the concrete generic views. If you're using generic views this is normally the level you'll be working at unless you need heavily customized behavior. -## ListAPIView +## CreateAPIView -Used for **read-only** endpoints to represent a **collection of model instances**. +Used for **create-only** endpoints. -Provides a `get` method handler. +Provides `post` method handlers. -Extends: [MultipleObjectBaseAPIView], [ListModelMixin] +Extends: [GenericAPIView], [CreateModelMixin] -## ListCreateAPIView +## ListAPIView -Used for **read-write** endpoints to represent a **collection of model instances**. +Used for **read-only** endpoints to represent a **collection of model instances**. -Provides `get` and `post` method handlers. +Provides a `get` method handler. -Extends: [MultipleObjectBaseAPIView], [ListModelMixin], [CreateModelMixin] +Extends: [MultipleObjectAPIView], [ListModelMixin] ## RetrieveAPIView @@ -71,7 +71,31 @@ Used for **read-only** endpoints to represent a **single model instance**. Provides a `get` method handler. -Extends: [SingleObjectBaseAPIView], [RetrieveModelMixin] +Extends: [SingleObjectAPIView], [RetrieveModelMixin] + +## DestroyAPIView + +Used for **delete-only** endpoints for a **single model instance**. + +Provides a `delete` method handler. + +Extends: [SingleObjectAPIView], [DestroyModelMixin] + +## UpdateAPIView + +Used for **update-only** endpoints for a **single model instance**. + +Provides a `put` method handler. + +Extends: [SingleObjectAPIView], [UpdateModelMixin] + +## ListCreateAPIView + +Used for **read-write** endpoints to represent a **collection of model instances**. + +Provides `get` and `post` method handlers. + +Extends: [MultipleObjectAPIView], [ListModelMixin], [CreateModelMixin] ## RetrieveDestroyAPIView @@ -79,15 +103,15 @@ Used for **read or delete** endpoints to represent a **single model instance**. Provides `get` and `delete` method handlers. -Extends: [SingleObjectBaseAPIView], [RetrieveModelMixin], [DestroyModelMixin] +Extends: [SingleObjectAPIView], [RetrieveModelMixin], [DestroyModelMixin] ## RetrieveUpdateDestroyAPIView -Used for **read-write** endpoints to represent a **single model instance**. +Used for **read-write-delete** endpoints to represent a **single model instance**. Provides `get`, `put` and `delete` method handlers. -Extends: [SingleObjectBaseAPIView], [RetrieveModelMixin], [UpdateModelMixin], [DestroyModelMixin] +Extends: [SingleObjectAPIView], [RetrieveModelMixin], [UpdateModelMixin], [DestroyModelMixin] --- @@ -121,7 +145,7 @@ The mixin classes provide the actions that are used to provide the basic view be Provides a `.list(request, *args, **kwargs)` method, that implements listing a queryset. -Should be mixed in with [MultipleObjectBaseAPIView]. +Should be mixed in with [MultipleObjectAPIView]. ## CreateModelMixin @@ -133,19 +157,19 @@ Should be mixed in with any [BaseAPIView]. Provides a `.retrieve(request, *args, **kwargs)` method, that implements returning an existing model instance in a response. -Should be mixed in with [SingleObjectBaseAPIView]. +Should be mixed in with [SingleObjectAPIView]. ## UpdateModelMixin Provides a `.update(request, *args, **kwargs)` method, that implements updating and saving an existing model instance. -Should be mixed in with [SingleObjectBaseAPIView]. +Should be mixed in with [SingleObjectAPIView]. ## DestroyModelMixin Provides a `.destroy(request, *args, **kwargs)` method, that implements deletion of an existing model instance. -Should be mixed in with [SingleObjectBaseAPIView]. +Should be mixed in with [SingleObjectAPIView]. [cite]: https://docs.djangoproject.com/en/dev/ref/class-based-views/#base-vs-generic-views [MultipleObjectMixin]: https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-multiple-object/ @@ -153,9 +177,9 @@ Should be mixed in with [SingleObjectBaseAPIView]. [multiple-object-mixin-classy]: http://ccbv.co.uk/projects/Django/1.4/django.views.generic.list/MultipleObjectMixin/ [single-object-mixin-classy]: http://ccbv.co.uk/projects/Django/1.4/django.views.generic.detail/SingleObjectMixin/ -[BaseAPIView]: #baseapiview -[SingleObjectBaseAPIView]: #singleobjectbaseapiview -[MultipleObjectBaseAPIView]: #multipleobjectbaseapiview +[GenericAPIView]: #genericapiview +[SingleObjectAPIView]: #singleobjectapiview +[MultipleObjectAPIView]: #multipleobjectapiview [ListModelMixin]: #listmodelmixin [CreateModelMixin]: #createmodelmixin [RetrieveModelMixin]: #retrievemodelmixin -- cgit v1.2.3 From 41d27b1a307132fea42fb104a8d7d266ea9e90d1 Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Thu, 25 Oct 2012 14:22:36 +0100 Subject: Fix section headings in generic views docs --- docs/api-guide/generic-views.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 7ca0f905..97b4441f 100644 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -119,17 +119,17 @@ Extends: [SingleObjectAPIView], [RetrieveModelMixin], [UpdateModelMixin], [Destr Each of the generic views provided is built by combining one of the base views below, with one or more mixin classes. -## BaseAPIView +## GenericAPIView Extends REST framework's `APIView` class, adding support for serialization of model instances and model querysets. -## MultipleObjectBaseAPIView +## MultipleObjectAPIView Provides a base view for acting on a single object, by combining REST framework's `APIView`, and Django's [MultipleObjectMixin]. **See also:** ccbv.co.uk documentation for [MultipleObjectMixin][multiple-object-mixin-classy]. -## SingleObjectBaseAPIView +## SingleObjectAPIView Provides a base view for acting on a single object, by combining REST framework's `APIView`, and Django's [SingleObjectMixin]. @@ -151,7 +151,7 @@ Should be mixed in with [MultipleObjectAPIView]. Provides a `.create(request, *args, **kwargs)` method, that implements creating and saving a new model instance. -Should be mixed in with any [BaseAPIView]. +Should be mixed in with any [GenericAPIView]. ## RetrieveModelMixin -- cgit v1.2.3 From 04ae32c9340b0782014bd61ef9ee3196af22ebce Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 25 Oct 2012 17:01:00 +0100 Subject: remove no-site-packages since that's now the default --- docs/tutorial/1-serialization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index 5b58f293..d1ae0ba5 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -10,7 +10,7 @@ Before we do anything else we'll create a new virtual environment, using [virtua :::bash mkdir ~/env - virtualenv --no-site-packages ~/env/tutorial + virtualenv ~/env/tutorial source ~/env/tutorial/bin/activate Now that we're inside a virtualenv environment, we can install our package requirements. -- cgit v1.2.3 From 195006bbc36c21f0154fe1ab7c46f339b2efe559 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Oct 2012 09:27:59 +0100 Subject: Drop resources from codebase since implementation is only partial (Created resoorces-routers branch for future reference) --- docs/tutorial/6-resource-orientated-projects.md | 76 ------------------------- 1 file changed, 76 deletions(-) delete mode 100644 docs/tutorial/6-resource-orientated-projects.md (limited to 'docs') diff --git a/docs/tutorial/6-resource-orientated-projects.md b/docs/tutorial/6-resource-orientated-projects.md deleted file mode 100644 index 9ee599ae..00000000 --- a/docs/tutorial/6-resource-orientated-projects.md +++ /dev/null @@ -1,76 +0,0 @@ -# Tutorial 6 - Resources - -Resource classes are just View classes that don't have any handler methods bound to them. The actions on a resource are defined, - -This allows us to: - -* Encapsulate common behaviour across a class of views, in a single Resource class. -* Separate out the actions of a Resource from the specifics of how those actions should be bound to a particular set of URLs. - -## Refactoring to use Resources, not Views - -For instance, we can re-write our 4 sets of views into something more compact... - -resources.py - - class BlogPostResource(ModelResource): - serializer_class = BlogPostSerializer - model = BlogPost - permissions_classes = (permissions.IsAuthenticatedOrReadOnly,) - throttle_classes = (throttles.UserRateThrottle,) - - class CommentResource(ModelResource): - serializer_class = CommentSerializer - model = Comment - permissions_classes = (permissions.IsAuthenticatedOrReadOnly,) - throttle_classes = (throttles.UserRateThrottle,) - -## Binding Resources to URLs explicitly -The handler methods only get bound to the actions when we define the URLConf. Here's our urls.py: - - comment_root = CommentResource.as_view(actions={ - 'get': 'list', - 'post': 'create' - }) - comment_instance = CommentInstance.as_view(actions={ - 'get': 'retrieve', - 'put': 'update', - 'delete': 'destroy' - }) - ... # And for blog post - - urlpatterns = patterns('blogpost.views', - url(r'^$', comment_root), - url(r'^(?P[0-9]+)$', comment_instance) - ... # And for blog post - ) - -## Using Routers - -Right now that hasn't really saved us a lot of code. However, now that we're using Resources rather than Views, we actually don't need to design the urlconf ourselves. The conventions for wiring up resources into views and urls can be handled automatically, using `Router` classes. All we need to do is register the appropriate resources with a router, and let it do the rest. Here's our re-wired `urls.py` file. - - from blog import resources - from rest_framework.routers import DefaultRouter - - router = DefaultRouter() - router.register(resources.BlogPostResource) - router.register(resources.CommentResource) - urlpatterns = router.urlpatterns - -## Trade-offs between views vs resources. - -Writing resource-oriented code can be a good thing. It helps ensure that URL conventions will be consistent across your APIs, and minimises the amount of code you need to write. - -The trade-off is that the behaviour is less explict. It can be more difficult to determine what code path is being followed, or where to override some behaviour. - -## Onwards and upwards. - -We've reached the end of our tutorial. If you want to get more involved in the REST framework project, here's a few places you can start: - -* Contribute on GitHub by reviewing issues, and submitting issues or pull requests. -* Join the REST framework group, and help build the community. -* Follow me [on Twitter][twitter] and say hi. - -**Now go build some awesome things.** - -[twitter]: https://twitter.com/_tomchristie -- cgit v1.2.3 From 365d20652eb7b56c9d1d4b63b52c056f03cab514 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Oct 2012 09:30:46 +0100 Subject: Add analytics --- docs/template.html | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/template.html b/docs/template.html index 45bada54..56c5d832 100644 --- a/docs/template.html +++ b/docs/template.html @@ -1,5 +1,6 @@ - + + Django REST framework @@ -17,6 +18,21 @@ + + +
-- cgit v1.2.3 From d4063eb02e31401252ca15b3aae50ed951d7869f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Oct 2012 12:46:31 +0100 Subject: Fix incorrect method signature in docs --- docs/api-guide/generic-views.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 97b4441f..360ef1a2 100644 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -30,7 +30,7 @@ For more complex cases you might also want to override various methods on the vi serializer_class = UserSerializer permission_classes = (IsAdminUser,) - def get_paginate_by(self): + def get_paginate_by(self, queryset): """ Use smaller pagination for HTML representations. """ -- cgit v1.2.3 From c221bc6f6f239d958e89523c00686da595c68578 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Oct 2012 17:30:45 +0200 Subject: Use context dict in HTMLRenderer --- docs/api-guide/renderers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index b6db376c..b5e2fe8f 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -130,7 +130,7 @@ An example of a view that uses `HTMLRenderer`: def get(self, request, *args, **kwargs) self.object = self.get_object() - return Response(self.object, template_name='user_detail.html') + return Response({'user': self.object}, template_name='user_detail.html') You can use `HTMLRenderer` either to return regular HTML pages using REST framework, or to return both HTML and API responses from a single endpoint. -- cgit v1.2.3 From 5180b725655e84712b103fbc280353d25048068b Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Sat, 27 Oct 2012 13:53:07 +0100 Subject: Documentation for function-based view decorators --- docs/api-guide/views.md | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md index e3fbadb2..c51860b5 100644 --- a/docs/api-guide/views.md +++ b/docs/api-guide/views.md @@ -118,9 +118,48 @@ You won't typically need to override this method. > > — [Nick Coghlan][cite2] -REST framework also gives you to work with regular function based views... +REST framework also allows you to work with regular function based views. It provides a set of simple decorators that wrap your function based views to ensure they receive an instance of `Request` (rather than the usual Django `HttpRequest`) and allows them to return a `Response` (instead of a Django `HttpResponse`), and allow you to configure how the request is processed. + +### api_view(http_method_names) + +The core of this functionality is the `api_view` decorator, which takes a list of HTTP methods that your view should respond to. For example, this is how you would write a very simple view that just manually returns some data: + + from rest_framework.decorators import api_view + + @api_view(['GET']) + def hello_world(request): + return Response({"message": "Hello, world!"}) + + +This view will use the default renderers, parsers, authentication classes etc specified in the [settings](settings). + +To override the default settings, REST framework provides a set of additional decorators which can be added to your views. These must come *after* (below) the `api_view` decorator. For example, to create a view that uses a [throttle](throttling) to ensure it can only be called once per day by a particular user, use the `throttle_classes` decorator, passing a list of throttle classes: + + from rest_framework.decorators import api_view, throttle_classes + from rest_framework.throttling import UserRateThrottle + + class OncePerDayUserThrottle(UserRateThrottle): + rate = '1/day' + + @api_view(['GET']) + @throttle_classes([OncePerDayUserThrottle]) + def view(request): + return Response({"message": "Hello for today! See you tomorrow!"}) + +These decorators correspond to the attributes set on `APIView` subclasses, described above. + +### renderer_classes(renderer_classes) + +### parser_classes(parser_classes) + +### authentication_classes(authentication_classes) + +### throttle_classes(throttle_classes) + +### permission_classes(permission_classes) -**[TODO]** [cite]: http://reinout.vanrees.org/weblog/2011/08/24/class-based-views-usage.html [cite2]: http://www.boredomandlaziness.org/2012/05/djangos-cbvs-are-not-mistake-but.html +[settings]: api-guide/settings.md +[throttling]: api-guide/throttling.md -- cgit v1.2.3 From ec1429ffc8079e2f0fcc5af05882360689fca5bf Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 27 Oct 2012 17:27:12 +0200 Subject: Tweaks --- docs/api-guide/views.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md index c51860b5..9e661532 100644 --- a/docs/api-guide/views.md +++ b/docs/api-guide/views.md @@ -148,15 +148,15 @@ To override the default settings, REST framework provides a set of additional de These decorators correspond to the attributes set on `APIView` subclasses, described above. -### renderer_classes(renderer_classes) +### @renderer_classes() -### parser_classes(parser_classes) +### @parser_classes() -### authentication_classes(authentication_classes) +### @authentication_classes() -### throttle_classes(throttle_classes) +### @throttle_classes() -### permission_classes(permission_classes) +### @permission_classes() [cite]: http://reinout.vanrees.org/weblog/2011/08/24/class-based-views-usage.html -- cgit v1.2.3 From cef379db065711bd2f1b0805d28a56f7a80cef37 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 27 Oct 2012 18:39:17 +0100 Subject: 2.0 Announcement --- docs/api-guide/views.md | 23 ++++---- docs/index.md | 4 +- docs/template.html | 2 +- docs/topics/rest-framework-2-announcement.md | 88 ++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 13 deletions(-) create mode 100644 docs/topics/rest-framework-2-announcement.md (limited to 'docs') diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md index 9e661532..96ce3be7 100644 --- a/docs/api-guide/views.md +++ b/docs/api-guide/views.md @@ -120,7 +120,9 @@ You won't typically need to override this method. REST framework also allows you to work with regular function based views. It provides a set of simple decorators that wrap your function based views to ensure they receive an instance of `Request` (rather than the usual Django `HttpRequest`) and allows them to return a `Response` (instead of a Django `HttpResponse`), and allow you to configure how the request is processed. -### api_view(http_method_names) +## @api_view() + +**Signature:** `@api_view(http_method_names) The core of this functionality is the `api_view` decorator, which takes a list of HTTP methods that your view should respond to. For example, this is how you would write a very simple view that just manually returns some data: @@ -133,7 +135,9 @@ The core of this functionality is the `api_view` decorator, which takes a list o This view will use the default renderers, parsers, authentication classes etc specified in the [settings](settings). -To override the default settings, REST framework provides a set of additional decorators which can be added to your views. These must come *after* (below) the `api_view` decorator. For example, to create a view that uses a [throttle](throttling) to ensure it can only be called once per day by a particular user, use the `throttle_classes` decorator, passing a list of throttle classes: +## API policy decorators + +To override the default settings, REST framework provides a set of additional decorators which can be added to your views. These must come *after* (below) the `@api_view` decorator. For example, to create a view that uses a [throttle](throttling) to ensure it can only be called once per day by a particular user, use the `@throttle_classes` decorator, passing a list of throttle classes: from rest_framework.decorators import api_view, throttle_classes from rest_framework.throttling import UserRateThrottle @@ -148,16 +152,15 @@ To override the default settings, REST framework provides a set of additional de These decorators correspond to the attributes set on `APIView` subclasses, described above. -### @renderer_classes() - -### @parser_classes() - -### @authentication_classes() - -### @throttle_classes() +The available decorators are: -### @permission_classes() +* `@renderer_classes(...)` +* `@parser_classes(...)` +* `@authentication_classes(...)` +* `@throttle_classes(...)` +* `@permission_classes(...)` +Each of these decorators takes a single argument which must be a list or tuple of classes. [cite]: http://reinout.vanrees.org/weblog/2011/08/24/class-based-views-usage.html [cite2]: http://www.boredomandlaziness.org/2012/05/djangos-cbvs-are-not-mistake-but.html diff --git a/docs/index.md b/docs/index.md index f66ba7f4..a75dbc58 100644 --- a/docs/index.md +++ b/docs/index.md @@ -103,7 +103,7 @@ General guides to using REST framework. * [The Browsable API][browsableapi] * [REST, Hypermedia & HATEOAS][rest-hypermedia-hateoas] * [Contributing to REST framework][contributing] -* [2.0 Migration Guide][migration] +* [2.0 Announcement][rest-framework-2-announcement] * [Release Notes][release-notes] * [Credits][credits] @@ -189,7 +189,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [browsableapi]: topics/browsable-api.md [rest-hypermedia-hateoas]: topics/rest-hypermedia-hateoas.md [contributing]: topics/contributing.md -[migration]: topics/migration.md +[rest-framework-2-announcement]: topics/rest-framework-2-announcement.md [release-notes]: topics/release-notes.md [credits]: topics/credits.md diff --git a/docs/template.html b/docs/template.html index 56c5d832..91a7a731 100644 --- a/docs/template.html +++ b/docs/template.html @@ -93,7 +93,7 @@
  • The Browsable API
  • REST, Hypermedia & HATEOAS
  • Contributing to REST framework
  • -
  • 2.0 Migration Guide
  • +
  • 2.0 Announcement
  • Release Notes
  • Credits
  • diff --git a/docs/topics/rest-framework-2-announcement.md b/docs/topics/rest-framework-2-announcement.md new file mode 100644 index 00000000..98e66228 --- /dev/null +++ b/docs/topics/rest-framework-2-announcement.md @@ -0,0 +1,88 @@ +# Django REST framework 2 + +What it is, and why you should care + +> Most people just make the mistake that it should be simple to design simple things. In reality, the effort required to design something is inversely proportional to the simplicity of the result. +> +> — [Roy Fielding][cite] + +REST framework 2 is an almost complete reworking of the original framework, which comprehensivly addresses some of the original design issues. + +Because the latest version should be considered a re-release, rather than an incremental improvement, we've skipped a version, and called this release Django REST framework 2.0. + +This article is intended to give you a flavor of what REST framework 2 is, and why you might want to give it a try. + +## User feedback + +Before we get cracking, let's start with the hard sell, with a few bits of feedback from some early adopters… + +"Django REST framework 2 is beautiful. Some of the API design is worthy of @kennethreitz." - [Kit La Touche][quote1] + +"Since it's pretty much just Django, controlling things like URLs has been a breeze... I think [REST framework 2] has definitely got the right approach here; even simple things like being able to override a function called post to do custom work during rather than having to intimately know what happens during a post make a huge difference to your productivity." - [Ian Strachan][quote2] + +"I switched to the 2.0 branch and I don't regret it - fully refactored my code in another ½ day and it's *much* more to my tastes" - [Bruno Desthuilliers][quote3] + +Sounds good, right? Let's get into some details... + +## Serialization + +REST framework 2 includes a totally re-worked serialization engine, that was initially intended as a replacement for Django's existing inflexible fixture serialization, and which meets the following design goals: + +* A declarative serialization API, that mirrors Django's `Forms`/`ModelForms` API. +* Structural concerns are decoupled from encoding concerns. +* Able to support rendering and parsing to many formats, including both machine-readable representations and HTML forms. +* Validation that can be mapped to obvious and comprehensive error responses. +* Serializers that support both nested, flat, and partially-nested representations. +* Relationships that can be expressed as primary keys, hyperlinks, slug fields, and other custom representations. + +Mapping between the internal state of the system and external representations of that state is the core concern of building Web APIs. Designing serializers that allow the developer to do so in a flexible and obvious way is a deceptively difficult design task, and with the new serialization API we think we've pretty much nailed it. + +## Generic views + +When REST framework was initially released at the start of 2011, the current Django release was version 1.2. REST framework included a backport of Django 1.3's upcoming `View` class, but it didn't take full advantage of the generic view implementations. + +With the new release the generic views in REST framework now tie in with Django's generic views. The end result is that framework is clean, lightweight and easy to use. + +## Requests, Responses & Views + +REST framework 2 includes `Request` and `Response` classes, than are used in place of Django's existing `HttpRequest` and `HttpResponse` classes. Doing so allows logic such as parsing the incoming request or rendering the outgoing response to be supported transparently by the framework. + +The `Request`/`Response` approach leads to a much cleaner API, less logic in the view itself, and a simple, obvious request-response cycle. + +REST framework 2 also allows you to work with both function-based and class-based views. For simple API views all you need is a single `@api_view` decorator, and you're good to go. + + +## API Design + +Pretty much every aspect of REST framework has been reworked, with the aim of ironing out some of the design flaws of the previous versions. Each of the components of REST framework are cleanly decoupled, and can be used independantly of each-other, and there are no monolithic resource classes, overcomplicated mixin combinations, or opinionated serialization or URL routing decisions. + +## Documentation + +As you can see the documentation for REST framework has been radically improved. It gets a completely new style, using markdown for the documentation source, and a bootstrap-based theme for the styling. + +In the author's opinion, using `markdown` for documentation is a much better option that `rst`. It is intuitive and readable, and there is great tooling available, such as the [Mou][mou] editor for Mac OS X, which makes it easy and plesant to work. + +We're really pleased with how the docs style looks - it's simple and clean, and the docs build much more quickly than with the previous sphinx setup. We'll miss being able to use the wonderful [Read the Docs][readthedocs] service, but we think it's a trade-off worth making. + +Developing REST framework's documentation builder into a fully-fledged reusable project is something that we have planned for a future date. + +## The Browseable API + +Django REST framework's most unique feature is the way it is able to serve up both machine-readable representations, and a fully browsable HTML representation to the same endpoints. + +Browseable Web APIs are easier to work with, visualize and debug, and generally makes it easier and more frictionless to inspect and work with. + +With REST framework 2, the browseable API gets a snazzy new bootstrap-based theme that looks great and is even nicer to work with. + +There are also some functionality improvments - actions such as as `POST` and `DELETE` will only display if the user has the appropriate permissions. + +## Summary + +In short, we've engineered the hell outta this thing, and we're incredibly proud of the result. + +[cite]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-724 +[quote1]: https://twitter.com/kobutsu/status/261689665952833536 +[quote2]: https://groups.google.com/d/msg/django-rest-framework/heRGHzG6BWQ/ooVURgpwVC0J +[quote3]: https://groups.google.com/d/msg/django-rest-framework/flsXbvYqRoY/9lSyntOf5cUJ +[mou]: http://mouapp.com/ +[readthedocs]: https://readthedocs.org/ -- cgit v1.2.3 From 51a64019260d99e3c615b407353e344cf615da1e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 27 Oct 2012 18:47:17 +0100 Subject: Added @madisvain. Thanks! --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) (limited to 'docs') diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 27a56326..a317afde 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -49,6 +49,7 @@ The following people have helped make REST framework great. * Tomi Pajunen - [eofs] * Rob Dobson - [rdobson] * Daniel Vaca Araujo - [diviei] +* Madis Väin - [madisvain] Many thanks to everyone who's contributed to the project. @@ -129,3 +130,4 @@ To contact the author directly: [eofs]: https://github.com/eofs [rdobson]: https://github.com/rdobson [diviei]: https://github.com/diviei +[madisvain]: https://github.com/madisvain -- cgit v1.2.3 From d995742afc09ff8d387751a6fe47b9686845740b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 27 Oct 2012 20:04:33 +0100 Subject: Add AllowAny permission --- docs/api-guide/permissions.md | 12 ++++++++++++ docs/api-guide/settings.md | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 0b7b32e9..d43b7bed 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -33,6 +33,12 @@ The default permission policy may be set globally, using the `DEFAULT_PERMISSION ) } +If not specified, this setting defaults to allowing unrestricted access: + + 'DEFAULT_PERMISSION_CLASSES': ( + 'rest_framework.permissions.AllowAny', + ) + You can also set the authentication policy on a per-view basis, using the `APIView` class based views. class ExampleView(APIView): @@ -58,6 +64,12 @@ Or, if you're using the `@api_view` decorator with function based views. # API Reference +## AllowAny + +The `AllowAny` permission class will allow unrestricted access, **regardless of if the request was authenticated or unauthenticated**. + +This permission is not strictly required, since you can achieve the same result by using an empty list or tuple for the permissions setting, but you may find it useful to specify this class because it makes the intention explicit. + ## IsAuthenticated The `IsAuthenticated` permission class will deny permission to any unauthenticated user, and allow permission otherwise. diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 21efc853..3556a5b1 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -72,7 +72,11 @@ Default: A list or tuple of permission classes, that determines the default set of permissions checked at the start of a view. -Default: `()` +Default: + + ( + 'rest_framework.permissions.AllowAny', + ) ## DEFAULT_THROTTLE_CLASSES -- cgit v1.2.3 From 12c363c1fe237d0357e6020b44890926856b9191 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sun, 28 Oct 2012 18:12:56 +0000 Subject: TemplateHTMLRenderer, StaticHTMLRenderer --- docs/api-guide/renderers.md | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index b5e2fe8f..5efb3610 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -106,12 +106,12 @@ If you are considering using `XML` for your API, you may want to consider implem **.format**: `'.xml'` -## HTMLRenderer +## TemplateHTMLRenderer Renders data to HTML, using Django's standard template rendering. Unlike other renderers, the data passed to the `Response` does not need to be serialized. Also, unlike other renderers, you may want to include a `template_name` argument when creating the `Response`. -The HTMLRenderer will create a `RequestContext`, using the `response.data` as the context dict, and determine a template name to use to render the context. +The TemplateHTMLRenderer will create a `RequestContext`, using the `response.data` as the context dict, and determine a template name to use to render the context. The template name is determined by (in order of preference): @@ -119,27 +119,49 @@ The template name is determined by (in order of preference): 2. An explicit `.template_name` attribute set on this class. 3. The return result of calling `view.get_template_names()`. -An example of a view that uses `HTMLRenderer`: +An example of a view that uses `TemplateHTMLRenderer`: class UserInstance(generics.RetrieveUserAPIView): """ A view that returns a templated HTML representations of a given user. """ model = Users - renderer_classes = (HTMLRenderer,) + renderer_classes = (TemplateHTMLRenderer,) def get(self, request, *args, **kwargs) self.object = self.get_object() return Response({'user': self.object}, template_name='user_detail.html') -You can use `HTMLRenderer` either to return regular HTML pages using REST framework, or to return both HTML and API responses from a single endpoint. +You can use `TemplateHTMLRenderer` either to return regular HTML pages using REST framework, or to return both HTML and API responses from a single endpoint. -If you're building websites that use `HTMLRenderer` along with other renderer classes, you should consider listing `HTMLRenderer` as the first class in the `renderer_classes` list, so that it will be prioritised first even for browsers that send poorly formed `ACCEPT:` headers. +If you're building websites that use `TemplateHTMLRenderer` along with other renderer classes, you should consider listing `TemplateHTMLRenderer` as the first class in the `renderer_classes` list, so that it will be prioritised first even for browsers that send poorly formed `ACCEPT:` headers. **.media_type**: `text/html` **.format**: `'.html'` +See also: `StaticHTMLRenderer` + +## StaticHTMLRenderer + +A simple renderer that simply returns pre-rendered HTML. Unlike other renderers, the data passed to the response object should be a string representing the content to be returned. + +An example of a view that uses `TemplateHTMLRenderer`: + + @api_view(('GET',)) + @renderer_classes((StaticHTMLRenderer,)) + def simple_html_view(request): + data = '

    Hello, world

    ' + return Response(data) + +You can use `TemplateHTMLRenderer` either to return regular HTML pages using REST framework, or to return both HTML and API responses from a single endpoint. + +**.media_type**: `text/html` + +**.format**: `'.html'` + +See also: `TemplateHTMLRenderer` + ## BrowsableAPIRenderer Renders data into HTML for the Browseable API. This renderer will determine which other renderer would have been given highest priority, and use that to display an API style response within the HTML page. @@ -207,7 +229,7 @@ In some cases you might want your view to use different serialization styles dep For example: @api_view(('GET',)) - @renderer_classes((HTMLRenderer, JSONRenderer)) + @renderer_classes((TemplateHTMLRenderer, JSONRenderer)) def list_users(request): """ A view that can return JSON or HTML representations @@ -215,9 +237,9 @@ For example: """ queryset = Users.objects.filter(active=True) - if request.accepted_media_type == 'text/html': + if request.accepted_renderer.format == 'html': # TemplateHTMLRenderer takes a context dict, - # and additionally requiresa 'template_name'. + # and additionally requires a 'template_name'. # It does not require serialization. data = {'users': queryset} return Response(data, template_name='list_users.html') -- cgit v1.2.3 From fde79376f323708d9f7b80ee830fe63060fb335f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sun, 28 Oct 2012 19:25:51 +0000 Subject: Pastebin tutorial --- docs/tutorial/1-serialization.md | 180 ++++++++++++-------- docs/tutorial/2-requests-and-responses.md | 45 +++-- docs/tutorial/3-class-based-views.md | 88 +++++----- docs/tutorial/4-authentication-and-permissions.md | 183 +++++++++++++++++++++ .../4-authentication-permissions-and-throttling.md | 5 - .../5-relationships-and-hyperlinked-apis.md | 158 +++++++++++++++++- 6 files changed, 512 insertions(+), 147 deletions(-) create mode 100644 docs/tutorial/4-authentication-and-permissions.md delete mode 100644 docs/tutorial/4-authentication-permissions-and-throttling.md (limited to 'docs') diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index d1ae0ba5..fc052202 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -2,7 +2,9 @@ ## Introduction -This tutorial will walk you through the building blocks that make up REST framework. It'll take a little while to get through, but it'll give you a comprehensive understanding of how everything fits together. +This tutorial will cover creating a simple pastebin code highlighting Web API. Along the way it will introduce the various components that make up REST framework, and give you a comprehensive understanding of how everything fits together. + +The tutorial is fairly in-depth, so you should probably get a cookie and a cup of your favorite brew before getting started. If you just want a quick overview, you should head over to the [quickstart] documentation instead. ## Setting up a new environment @@ -17,6 +19,7 @@ Now that we're inside a virtualenv environment, we can install our package requi pip install django pip install djangorestframework + pip install pygments # We'll be using this for the code highlighting **Note:** To exit the virtualenv environment at any time, just type `deactivate`. For more information see the [virtualenv documentation][virtualenv]. @@ -30,8 +33,9 @@ To get started, let's create a new project to work with. cd tutorial Once that's done we can create an app that we'll use to create a simple Web API. +We're going to create a project that - python manage.py startapp blog + python manage.py startapp snippets The simplest way to get up and running will probably be to use an `sqlite3` database for the tutorial. Edit the `tutorial/settings.py` file, and set the default database `"ENGINE"` to `"sqlite3"`, and `"NAME"` to `"tmp.db"`. @@ -46,32 +50,48 @@ The simplest way to get up and running will probably be to use an `sqlite3` data } } -We'll also need to add our new `blog` app and the `rest_framework` app to `INSTALLED_APPS`. +We'll also need to add our new `snippets` app and the `rest_framework` app to `INSTALLED_APPS`. INSTALLED_APPS = ( ... 'rest_framework', - 'blog' + 'snippets' ) -We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our blog views. +We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our snippet views. urlpatterns = patterns('', - url(r'^', include('blog.urls')), + url(r'^', include('snippets.urls')), ) Okay, we're ready to roll. ## Creating a model to work with -For the purposes of this tutorial we're going to start by creating a simple `Comment` model that is used to store comments against a blog post. Go ahead and edit the `blog` app's `models.py` file. +For the purposes of this tutorial we're going to start by creating a simple `Snippet` model that is used to store code snippets. Go ahead and edit the `snippets` app's `models.py` file. from django.db import models - - class Comment(models.Model): - email = models.EmailField() - content = models.CharField(max_length=200) + from pygments.lexers import get_all_lexers + from pygments.styles import get_all_styles + + LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in get_all_lexers()]) + STYLE_CHOICES = sorted((item, item) for item in list(get_all_styles())) + + + class Snippet(models.Model): created = models.DateTimeField(auto_now_add=True) + title = models.CharField(max_length=100, default='') + code = models.TextField() + linenos = models.BooleanField(default=False) + language = models.CharField(choices=LANGUAGE_CHOICES, + default='python', + max_length=100) + style = models.CharField(choices=STYLE_CHOICES, + default='friendly', + max_length=100) + + class Meta: + ordering = ('created',) Don't forget to sync the database for the first time. @@ -79,28 +99,40 @@ Don't forget to sync the database for the first time. ## Creating a Serializer class -We're going to create a simple Web API that we can use to edit these comment objects with. The first thing we need is a way of serializing and deserializing the objects into representations such as `json`. We do this by declaring serializers that work very similarly to Django's forms. Create a file in the `blog` directory named `serializers.py` and add the following. +The first thing we need to get started on our Web API is provide a way of serializing and deserializing the snippet instances into representations such as `json`. We can do this by declaring serializers that work very similarly to Django's forms. Create a file in the `snippets` directory named `serializers.py` and add the following. - from blog import models + from django.forms import widgets from rest_framework import serializers - - - class CommentSerializer(serializers.Serializer): - id = serializers.IntegerField(readonly=True) - email = serializers.EmailField() - content = serializers.CharField(max_length=200) - created = serializers.DateTimeField(readonly=True) - + from snippets import models + + + class SnippetSerializer(serializers.Serializer): + pk = serializers.Field() # Note: `Field` is an untyped read-only field. + title = serializers.CharField(required=False, + max_length=100) + code = serializers.CharField(widget=widgets.Textarea, + max_length=100000) + linenos = serializers.BooleanField(required=False) + language = serializers.ChoiceField(choices=models.LANGUAGE_CHOICES, + default='python') + style = serializers.ChoiceField(choices=models.STYLE_CHOICES, + default='friendly') + def restore_object(self, attrs, instance=None): """ - Create or update a new comment instance. + Create or update a new snippet instance. """ if instance: - instance.email = attrs['email'] - instance.content = attrs['content'] - instance.created = attrs['created'] + # Update existing instance + instance.title = attrs['title'] + instance.code = attrs['code'] + instance.linenos = attrs['linenos'] + instance.language = attrs['language'] + instance.style = attrs['style'] return instance - return models.Comment(**attrs) + + # Create new instance + return models.Snippet(**attrs) The first part of serializer class defines the fields that get serialized/deserialized. The `restore_object` method defines how fully fledged instances get created when deserializing data. @@ -112,31 +144,27 @@ Before we go any further we'll familiarise ourselves with using our new Serializ python manage.py shell -Okay, once we've got a few imports out of the way, we'd better create a few comments to work with. +Okay, once we've got a few imports out of the way, let's create a code snippet to work with. - from blog.models import Comment - from blog.serializers import CommentSerializer + from snippets.models import Snippet + from snippets.serializers import SnippetSerializer from rest_framework.renderers import JSONRenderer from rest_framework.parsers import JSONParser - c1 = Comment(email='leila@example.com', content='nothing to say') - c2 = Comment(email='tom@example.com', content='foo bar') - c3 = Comment(email='anna@example.com', content='LOLZ!') - c1.save() - c2.save() - c3.save() + snippet = Snippet(code='print "hello, world"\n') + snippet.save() -We've now got a few comment instances to play with. Let's take a look at serializing one of those instances. +We've now got a few snippet instances to play with. Let's take a look at serializing one of those instances. - serializer = CommentSerializer(instance=c1) + serializer = SnippetSerializer(instance=snippet) serializer.data - # {'id': 1, 'email': u'leila@example.com', 'content': u'nothing to say', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774, tzinfo=)} + # {'pk': 1, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'} At this point we've translated the model instance into python native datatypes. To finalise the serialization process we render the data into `json`. content = JSONRenderer().render(serializer.data) content - # '{"id": 1, "email": "leila@example.com", "content": "nothing to say", "created": "2012-08-22T16:20:09.822"}' + # '{"pk": 1, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}' Deserialization is similar. First we parse a stream into python native datatypes... @@ -147,28 +175,45 @@ Deserialization is similar. First we parse a stream into python native datatype ...then we restore those native datatypes into to a fully populated object instance. - serializer = CommentSerializer(data) + serializer = SnippetSerializer(data) serializer.is_valid() # True serializer.object - # + # Notice how similar the API is to working with forms. The similarity should become even more apparent when we start writing views that use our serializer. -## Writing regular Django views using our Serializers +## Using ModelSerializers + +Our `SnippetSerializer` class is replicating a lot of information that's also contained in the `Snippet` model. It would be nice if we could keep out code a bit more concise. + +In the same way that Django provides both `Form` classes and `ModelForm` classes, REST framework includes both `Serializer` classes, and `ModelSerializer` classes. + +Let's look at refactoring our serializer using the `ModelSerializer` class. +Open the file `snippets/serializers.py` again, and edit the `SnippetSerializer` class. + + class SnippetSerializer(serializers.ModelSerializer): + class Meta: + model = models.Snippet + fields = ('pk', 'title', 'code', 'linenos', 'language', 'style') + + + +## Writing regular Django views using our Serializer Let's see how we can write some API views using our new Serializer class. +For the moment we won't use any of REST framework's other features, we'll just write the views as regular Django views. + We'll start off by creating a subclass of HttpResponse that we can use to render any data we return into `json`. -Edit the `blog/views.py` file, and add the following. +Edit the `snippet/views.py` file, and add the following. - from blog.models import Comment - from blog.serializers import CommentSerializer from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from rest_framework.renderers import JSONRenderer from rest_framework.parsers import JSONParser - + from snippets.models import Snippet + from snippets.serializers import SnippetSerializer class JSONResponse(HttpResponse): """ @@ -181,67 +226,65 @@ Edit the `blog/views.py` file, and add the following. super(JSONResponse, self).__init__(content, **kwargs) -The root of our API is going to be a view that supports listing all the existing comments, or creating a new comment. +The root of our API is going to be a view that supports listing all the existing snippets, or creating a new snippet. @csrf_exempt - def comment_root(request): + def snippet_list(request): """ - List all comments, or create a new comment. + List all code snippets, or create a new snippet. """ if request.method == 'GET': - comments = Comment.objects.all() - serializer = CommentSerializer(instance=comments) + snippets = Snippet.objects.all() + serializer = SnippetSerializer(instance=snippets) return JSONResponse(serializer.data) elif request.method == 'POST': data = JSONParser().parse(request) - serializer = CommentSerializer(data) + serializer = SnippetSerializer(data) if serializer.is_valid(): - comment = serializer.object - comment.save() + serializer.save() return JSONResponse(serializer.data, status=201) else: return JSONResponse(serializer.errors, status=400) Note that because we want to be able to POST to this view from clients that won't have a CSRF token we need to mark the view as `csrf_exempt`. This isn't something that you'd normally want to do, and REST framework views actually use more sensible behavior than this, but it'll do for our purposes right now. -We'll also need a view which corresponds to an individual comment, and can be used to retrieve, update or delete the comment. +We'll also need a view which corresponds to an individual snippet, and can be used to retrieve, update or delete the snippet. @csrf_exempt - def comment_instance(request, pk): + def snippet_detail(request, pk): """ - Retrieve, update or delete a comment instance. + Retrieve, update or delete a code snippet. """ try: - comment = Comment.objects.get(pk=pk) - except Comment.DoesNotExist: + snippet = Snippet.objects.get(pk=pk) + except Snippet.DoesNotExist: return HttpResponse(status=404) if request.method == 'GET': - serializer = CommentSerializer(instance=comment) + serializer = SnippetSerializer(instance=snippet) return JSONResponse(serializer.data) elif request.method == 'PUT': data = JSONParser().parse(request) - serializer = CommentSerializer(data, instance=comment) + serializer = SnippetSerializer(data, instance=snippet) if serializer.is_valid(): - comment = serializer.object - comment.save() + serializer.save() return JSONResponse(serializer.data) else: return JSONResponse(serializer.errors, status=400) elif request.method == 'DELETE': - comment.delete() + snippet.delete() return HttpResponse(status=204) -Finally we need to wire these views up. Create the `blog/urls.py` file: +Finally we need to wire these views up. Create the `snippet/urls.py` file: from django.conf.urls import patterns, url - urlpatterns = patterns('blog.views', - url(r'^$', 'comment_root'), - url(r'^(?P[0-9]+)$', 'comment_instance') + urlpatterns = patterns('snippets.views', + url(r'^snippet/$', 'snippet_list'), + url(r'^snippet/(?P[0-9]+)/$', 'snippet_detail') ) It's worth noting that there's a couple of edge cases we're not dealing with properly at the moment. If we send malformed `json`, or if a request is made with a method that the view doesn't handle, then we'll end up with a 500 "server error" response. Still, this'll do for now. @@ -260,5 +303,6 @@ Our API views don't do anything particularly special at the moment, beyond serve We'll see how we can start to improve things in [part 2 of the tutorial][tut-2]. +[quickstart]: quickstart.md [virtualenv]: http://www.virtualenv.org/en/latest/index.html [tut-2]: 2-requests-and-responses.md diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md index fc37322a..76803d24 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -38,27 +38,27 @@ Okay, let's go ahead and start using these new components to write a few views. We don't need our `JSONResponse` class anymore, so go ahead and delete that. Once that's done we can start refactoring our views slightly. - from blog.models import Comment - from blog.serializers import CommentSerializer from rest_framework import status from rest_framework.decorators import api_view from rest_framework.response import Response + from snippet.models import Snippet + from snippet.serializers import SnippetSerializer + @api_view(['GET', 'POST']) - def comment_root(request): + def snippet_list(request): """ - List all comments, or create a new comment. + List all snippets, or create a new snippet. """ if request.method == 'GET': - comments = Comment.objects.all() - serializer = CommentSerializer(instance=comments) + snippets = Snippet.objects.all() + serializer = SnippetSerializer(instance=snippets) return Response(serializer.data) elif request.method == 'POST': - serializer = CommentSerializer(request.DATA) + serializer = SnippetSerializer(request.DATA) if serializer.is_valid(): - comment = serializer.object - comment.save() + serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -67,30 +67,29 @@ We don't need our `JSONResponse` class anymore, so go ahead and delete that. On Our instance view is an improvement over the previous example. It's a little more concise, and the code now feels very similar to if we were working with the Forms API. We're also using named status codes, which makes the response meanings more obvious. @api_view(['GET', 'PUT', 'DELETE']) - def comment_instance(request, pk): + def snippet_detail(request, pk): """ - Retrieve, update or delete a comment instance. + Retrieve, update or delete a snippet instance. """ try: - comment = Comment.objects.get(pk=pk) - except Comment.DoesNotExist: + snippet = Snippet.objects.get(pk=pk) + except Snippet.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) if request.method == 'GET': - serializer = CommentSerializer(instance=comment) + serializer = SnippetSerializer(instance=snippet) return Response(serializer.data) elif request.method == 'PUT': - serializer = CommentSerializer(request.DATA, instance=comment) + serializer = SnippetSerializer(request.DATA, instance=snippet) if serializer.is_valid(): - comment = serializer.object - comment.save() + serializer.save() return Response(serializer.data) else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) elif request.method == 'DELETE': - comment.delete() + snippet.delete() return Response(status=status.HTTP_204_NO_CONTENT) This should all feel very familiar - there's not a lot different to working with regular Django views. @@ -103,20 +102,20 @@ To take advantage of the fact that our responses are no longer hardwired to a si Start by adding a `format` keyword argument to both of the views, like so. - def comment_root(request, format=None): + def snippet_list(request, format=None): and - def comment_instance(request, pk, format=None): + def snippet_detail(request, pk, format=None): Now update the `urls.py` file slightly, to append a set of `format_suffix_patterns` in addition to the existing URLs. from django.conf.urls import patterns, url from rest_framework.urlpatterns import format_suffix_patterns - urlpatterns = patterns('blog.views', - url(r'^$', 'comment_root'), - url(r'^(?P[0-9]+)$', 'comment_instance') + urlpatterns = patterns('snippet.views', + url(r'^$', 'snippet_list'), + url(r'^(?P[0-9]+)$', 'snippet_detail') ) urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md index 0ee81ea3..3d58fe8e 100644 --- a/docs/tutorial/3-class-based-views.md +++ b/docs/tutorial/3-class-based-views.md @@ -6,61 +6,59 @@ We can also write our API views using class based views, rather than function ba We'll start by rewriting the root view as a class based view. All this involves is a little bit of refactoring. - from blog.models import Comment - from blog.serializers import CommentSerializer + from snippet.models import Snippet + from snippet.serializers import SnippetSerializer from django.http import Http404 from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status - class CommentRoot(APIView): + class SnippetList(APIView): """ - List all comments, or create a new comment. + List all snippets, or create a new snippet. """ def get(self, request, format=None): - comments = Comment.objects.all() - serializer = CommentSerializer(instance=comments) + snippets = Snippet.objects.all() + serializer = SnippetSerializer(instance=snippets) return Response(serializer.data) def post(self, request, format=None): - serializer = CommentSerializer(request.DATA) + serializer = SnippetSerializer(request.DATA) if serializer.is_valid(): - comment = serializer.object - comment.save() + serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) So far, so good. It looks pretty similar to the previous case, but we've got better separation between the different HTTP methods. We'll also need to update the instance view. - class CommentInstance(APIView): + class SnippetDetail(APIView): """ - Retrieve, update or delete a comment instance. + Retrieve, update or delete a snippet instance. """ def get_object(self, pk): try: - return Comment.objects.get(pk=pk) - except Comment.DoesNotExist: + return Snippet.objects.get(pk=pk) + except Snippet.DoesNotExist: raise Http404 def get(self, request, pk, format=None): - comment = self.get_object(pk) - serializer = CommentSerializer(instance=comment) + snippet = self.get_object(pk) + serializer = SnippetSerializer(instance=snippet) return Response(serializer.data) def put(self, request, pk, format=None): - comment = self.get_object(pk) - serializer = CommentSerializer(request.DATA, instance=comment) + snippet = self.get_object(pk) + serializer = SnippetSerializer(request.DATA, instance=snippet) if serializer.is_valid(): - comment = serializer.object - comment.save() + serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def delete(self, request, pk, format=None): - comment = self.get_object(pk) - comment.delete() + snippet = self.get_object(pk) + snippet.delete() return Response(status=status.HTTP_204_NO_CONTENT) That's looking good. Again, it's still pretty similar to the function based view right now. @@ -69,11 +67,11 @@ We'll also need to refactor our URLconf slightly now we're using class based vie from django.conf.urls import patterns, url from rest_framework.urlpatterns import format_suffix_patterns - from blogpost import views + from snippetpost import views urlpatterns = patterns('', - url(r'^$', views.CommentRoot.as_view()), - url(r'^(?P[0-9]+)$', views.CommentInstance.as_view()) + url(r'^$', views.SnippetList.as_view()), + url(r'^(?P[0-9]+)$', views.SnippetDetail.as_view()) ) urlpatterns = format_suffix_patterns(urlpatterns) @@ -88,16 +86,16 @@ The create/retrieve/update/delete operations that we've been using so far are go Let's take a look at how we can compose our views by using the mixin classes. - from blog.models import Comment - from blog.serializers import CommentSerializer + from snippet.models import Snippet + from snippet.serializers import SnippetSerializer from rest_framework import mixins from rest_framework import generics - class CommentRoot(mixins.ListModelMixin, + class SnippetList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.MultipleObjectBaseView): - model = Comment - serializer_class = CommentSerializer + model = Snippet + serializer_class = SnippetSerializer def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) @@ -109,12 +107,12 @@ We'll take a moment to examine exactly what's happening here - We're building ou The base class provides the core functionality, and the mixin classes provide the `.list()` and `.create()` actions. We're then explicitly binding the `get` and `post` methods to the appropriate actions. Simple enough stuff so far. - class CommentInstance(mixins.RetrieveModelMixin, - mixins.UpdateModelMixin, - mixins.DestroyModelMixin, - generics.SingleObjectBaseView): - model = Comment - serializer_class = CommentSerializer + class SnippetDetail(mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + generics.SingleObjectBaseView): + model = Snippet + serializer_class = SnippetSerializer def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) @@ -131,23 +129,23 @@ Pretty similar. This time we're using the `SingleObjectBaseView` class to provi Using the mixin classes we've rewritten the views to use slightly less code than before, but we can go one step further. REST framework provides a set of already mixed-in generic views that we can use. - from blog.models import Comment - from blog.serializers import CommentSerializer + from snippet.models import Snippet + from snippet.serializers import SnippetSerializer from rest_framework import generics - class CommentRoot(generics.ListCreateAPIView): - model = Comment - serializer_class = CommentSerializer + class SnippetList(generics.ListCreateAPIView): + model = Snippet + serializer_class = SnippetSerializer - class CommentInstance(generics.RetrieveUpdateDestroyAPIView): - model = Comment - serializer_class = CommentSerializer + class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): + model = Snippet + serializer_class = SnippetSerializer Wow, that's pretty concise. We've got a huge amount for free, and our code looks like good, clean, idiomatic Django. -Next we'll move onto [part 4 of the tutorial][tut-4], where we'll take a look at how we can customize the behavior of our views to support a range of authentication, permissions, throttling and other aspects. +Next we'll move onto [part 4 of the tutorial][tut-4], where we'll take a look at how we can deal with authentication and permissions for our API. [dry]: http://en.wikipedia.org/wiki/Don't_repeat_yourself -[tut-4]: 4-authentication-permissions-and-throttling.md +[tut-4]: 4-authentication-and-permissions.md diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md new file mode 100644 index 00000000..79436ad4 --- /dev/null +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -0,0 +1,183 @@ +# Tutorial 4: Authentication & Permissions + +Currently our API doesn't have any restrictions on who can + + +## Adding information to our model + +We're going to make a couple of changes to our `Snippet` model class. +First, let's add a couple of fields. One of those fields will be used to represent the user who created the code snippet. The other field will be used to store the highlighted HTML representation of the code. + +Add the following two fields to the model. + + owner = models.ForeignKey('auth.User', related_name='snippets') + highlighted = models.TextField() + +We'd also need to make sure that when the model is saved, that we populate the highlighted field, using the `pygments` code higlighting library. + +We'll ned some extra imports: + + from pygments.lexers import get_lexer_by_name + from pygments.formatters import HtmlFormatter + from pygments import highlight + +And now we can add a `.save()` method to our model class: + + def save(self, *args, **kwargs): + """ + Use the `pygments` library to create an highlighted HTML + representation of the code snippet. + """ + lexer = get_lexer_by_name(self.language) + linenos = self.linenos and 'table' or False + options = self.title and {'title': self.title} or {} + formatter = HtmlFormatter(style=self.style, linenos=linenos, + full=True, **options) + self.highlighted = highlight(self.code, lexer, formatter) + super(Snippet, self).save(*args, **kwargs) + +When that's all done we'll need to update our database tables. +Normally we'd create a database migration in order to do that, but for the purposes of this tutorial, let's just delete the database and start again. + + rm tmp.db + python ./manage.py syncdb + +You might also want to create a few different users, to use for testing the API. The quickest way to do this will be with the `createsuperuser` command. + + python ./manage.py createsuperuser + +## Adding endpoints for our User models + +Now that we've got some users to work with, we'd better add representations of those users to our API. Creating a new serializer is easy: + + class UserSerializer(serializers.ModelSerializer): + snippets = serializers.ManyPrimaryKeyRelatedField() + + class Meta: + model = User + fields = ('pk', 'username', 'snippets') + +Because `'snippets'` is a *reverse* relationship on the User model, it will not be included by default when using the `ModelSerializer` class, so we've needed to add an explicit field for it. + +We'll also add a couple of views. We'd like to just use read-only views for the user representations, so we'll use the `ListAPIView` and `RetrieveAPIView` generic class based views. + + class UserList(generics.ListAPIView): + model = User + serializer_class = UserSerializer + + + class UserInstance(generics.RetrieveAPIView): + model = User + serializer_class = UserSerializer + +Finally we need to add those views into the API, by referencing them from the URL conf. + + url(r'^users/$', views.UserList.as_view()), + url(r'^users/(?P[0-9]+)/$', views.UserInstance.as_view()) + +## Associating Snippets with Users + +Right now, if we created a code snippet, there'd be no way of associating the user that created the snippet, with the snippet instance. The user isn't sent as part of the serialized representation, but is instead a property of the incoming request. + +The way we deal with that is by overriding a `.pre_save()` method on our snippet views, that allows us to handle any information that is implicit in the incoming request or requested URL. + +On **both** the `SnippetList` and `SnippetInstance` view classes, add the following method: + + def pre_save(self, obj): + obj.owner = self.request.user + +## Updating our serializer + +Now that snippets are associated with the user that created them, let's update our SnippetSerializer to reflect that. + +Add the following field to the serializer definition: + + owner = serializers.Field(source='owner.username') + +**Note**: Make sure you also add `'owner',` to the list of fields in the inner `Meta` class. + +This field is doing something quite interesting. The `source` argument controls which attribtue is used to populate a field, and can point at any attribute on the serialized instance. It can also take the dotted notation shown above, in which case it will traverse the given attributes, in a similar way as is used with Django's template language. + +The field we've added is the untyped `Field` class, in contrast to the other typed fields, such as `CharField`, `BooleanField` etc... The untyped `Field` is always read-only, and will be used for serialized representations, but will not be used for updating model instances when they are deserialized. + +## Adding required permissions to views + +Now that code snippets are associated with users we want to make sure that only authenticated users are able to create, update and delete code snippets. + +REST framework includes a number of permission classes that we can use to restrict who can access a given view. In this case the one we're looking for is `IsAuthenticatedOrReadOnly`, which will ensure that authenticated requests get read-write access, and unauthenticated requests get read-only access. + +Add the following property to **both** the `SnippetList` and `SnippetInstance` view classes. + + permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + +**TODO: Now that the permissions are restricted, demonstrate using HTTP basic authentication with curl requests** + +## Adding login to the Browseable API + +If you open a browser and navigate to the browseable API at the moment, you'll find you're no longer able to create new code snippets. In order to do so we'd need to be able to login as a user. + +We can add a login view for use with the browseable API, by editing our URLconf once more. + +Add the following import at the top of the file: + + from django.conf.urls import include + +And, at the end of the file, add a pattern to include the login and logout views for the browseable API. + + urlpatterns += patterns('', + url(r'^api-auth/', include('rest_framework.urls', + namespace='rest_framework')) + ) + +The `r'^api-auth/'` part of pattern can actually be whatever URL you want to use. The only restriction is that the included urls must use the `'rest_framework'` namespace. + +Now if you open up the browser again and refresh the page you'll see a 'Login' link in the top right of the page. If you log in as one of the users you created earier, you'll be able to create code snippets again. + +Once you've created a few code snippets, navigate to the '/users/' endpoint, and notice that the representation includes a list of the snippet pks that are associated with each user, in each user's 'snippets' field. + +## Object level permissions + +Really we'd like all code snippets to be visible to anyone, but also make sure that only the user that created a code snippet is able update or delete it. + +To do that we're going to need to create a custom permission. + +In the snippets app, create a new file, `permissions.py` + + from rest_framework import permissions + + + class IsOwnerOrReadOnly(permissions.BasePermission): + """ + Custom permission to only allow owners of an object to edit it. + """ + + def has_permission(self, request, view, obj=None): + # Skip the check unless this is an object-level test + if obj is None: + return True + + # Read permissions are allowed to any request + if request.method in permissions.SAFE_METHODS: + return True + + # Write permissions are only allowed to the owner of the snippet + return obj.owner == request.user + +Now we can add that custom permission to our snippet instance endpoint, by editing the `permission_classes` property on the `SnippetInstance` class: + + permission_classes = (permissions.IsAuthenticatedOrReadOnly, + IsOwnerOrReadOnly,) + +Make sure to also import the `IsOwnerOrReadOnly` class. + + from snippets.permissions import IsOwnerOrReadOnly + +Now, if you open a browser again, you find that the 'DELETE' and 'PUT' actions only appear on a snippet instance endpoint if you're logged in as the same user that created the code snippet. + +## Summary + +We've now got a fairly fine-grained set of permissions on our Web API, and end points for users of the system and for the code snippets that they have created. + +In [part 5][tut-5] of the tutorial we'll look at how we can tie everything together by creating an HTML endpoint for our hightlighted snippets, and improve the cohesion of our API by using hyperlinking for the relationships within the system. + +[tut-5]: 5-relationships-and-hyperlinked-apis.md \ No newline at end of file diff --git a/docs/tutorial/4-authentication-permissions-and-throttling.md b/docs/tutorial/4-authentication-permissions-and-throttling.md deleted file mode 100644 index c8d7cbd3..00000000 --- a/docs/tutorial/4-authentication-permissions-and-throttling.md +++ /dev/null @@ -1,5 +0,0 @@ -# Tutorial 4: Authentication & Permissions - -Nothing to see here. Onwards to [part 5][tut-5]. - -[tut-5]: 5-relationships-and-hyperlinked-apis.md \ No newline at end of file diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md index 8600d5ed..84d02a53 100644 --- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md @@ -1,11 +1,157 @@ # Tutorial 5 - Relationships & Hyperlinked APIs -**TODO** +At the moment relationships within our API are represented by using primary keys. In this part of the tutorial we'll improve the cohesion and discoverability of our API, by instead using hyperlinking for relationships. -* Create BlogPost model -* Demonstrate nested relationships -* Demonstrate and describe hyperlinked relationships +## Creating an endpoint for the root of our API - + from rest_framework import renderers + from rest_framework.decorators import api_view + from rest_framework.response import Response + from rest_framework.reverse import reverse + + + @api_view(('GET',)) + def api_root(request, format=None): + return Response({ + 'users': reverse('user-list', request=request), + 'snippets': reverse('snippet-list', request=request) + }) + +Notice that we're using REST framework's `reverse` function in order to return fully-qualified URLs. + +## Creating an endpoint for the highlighted snippets + +The other obvious thing that's still missing from our pastebin API is the code highlighting endpoints. + +Unlike all our other API endpoints, we don't want to use JSON, but instead just present an HTML representation. There are two style of HTML renderer provided by REST framework, one for dealing with HTML rendered using templates, the other for dealing with pre-rendered HTML. The second renderer is the one we'd like to use for this endpoint. + +The other thing we need to consider when creating the code highlight view is that there's no existing concreate generic view that we can use. We're not returning an object instance, but instead a property of an object instance. + +Instead of using a concrete generic view, we'll use the base class for representing instances, and create our own `.get()` method. + + class SnippetHighlight(generics.SingleObjectAPIView): + model = Snippet + renderer_classes = (renderers.StaticHTMLRenderer,) + + def get(self, request, *args, **kwargs): + snippet = self.get_object() + return Response(snippet.highlighted) + +As usual we need to add the new views that we've created in to our URLconf. +We'll add a url pattern for our new API root: + + url(r'^$', 'api_root'), + +And then add a url pattern for the snippet highlights: + + url(r'^snippets/(?P[0-9]+)/highlight/$', views.SnippetHighlight.as_view()), + +## Hyperlinking our API + +Dealing with relationships between entities is one of the more challenging aspects of Web API design. There are a number of different ways that we might choose to represent a relationship: + +* Using primary keys. +* Using hyperlinking between entities. +* Using a unique identifying slug field on the related entity. +* Using the default string representation of the related entity. +* Nesting the related entity inside the parent representation. +* Some other custom representation. + +REST framework supports all of these styles, and can apply them across forward or reverse relationships, or apply them across custom managers such as generic foreign keys. + +In this case we'd like to use a hyperlinked style between entities. In order to do so, we'll modify our serializers to extend `HyperlinkedModelSerializer` instead of the existing `ModelSerializer`. + +The `HyperlinkedModelSerializer` has the following differences from `ModelSerializer`: + +* It does not include the `pk` field by default. +* It includes a `url` field, using `HyperlinkedIdentityField`. +* Relationships use `HyperlinkedRelatedField` and `ManyHyperlinkedRelatedField`, + instead of `PrimaryKeyRelatedField` and `ManyPrimaryKeyRelatedField`. + +We can easily re-write our existing serializers to use hyperlinking. + + class SnippetSerializer(serializers.HyperlinkedModelSerializer): + owner = serializers.Field(source='owner.username') + highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight') + + class Meta: + model = models.Snippet + fields = ('url', 'highlight', 'owner', + 'title', 'code', 'linenos', 'language', 'style') + + + class UserSerializer(serializers.HyperlinkedModelSerializer): + snippets = serializers.ManyHyperlinkedRelatedField(view_name='snippet-detail') + + class Meta: + model = User + fields = ('url', 'username', 'snippets') + +Notice that we've also added a new `'highlight'` field. This field is of the same type as the `url` field, except that it points to the `'snippet-highlight'` url pattern, instead of the `'snippet-detail'` url pattern. + +## Making sure our URL patterns are named + +If we're going to have a hyperlinked API, we need to make sure we name our URL patterns. Let's take a look at which URL patterns we need to name. + +* The root of our API refers to `'user-list'` and `'snippet-list'`. +* Our snippet serializer includes a field that refers to `'snippet-highlight'`. +* Our user serializer includes a field that refers to `'snippet-detail'`. +* Our snippet and user serializers include `'url'` fields that by default will refer to `'{model_name}-detail'`, which in this case will be `'snippet-detail'` and `'user-detail'`. + +After adding all those names into our URLconf, our final `'urls.py'` file should look something like this: + + # API endpoints + urlpatterns = format_suffix_patterns(patterns('snippets.views', + url(r'^$', 'api_root'), + url(r'^snippets/$', + views.SnippetList.as_view(), + name='snippet-list'), + url(r'^snippets/(?P[0-9]+)/$', + views.SnippetInstance.as_view(), + name='snippet-detail'), + url(r'^snippets/(?P[0-9]+)/highlight/$' + views.SnippetHighlight.as_view(), + name='snippet-highlight'), + url(r'^users/$', + views.UserList.as_view(), + name='user-list'), + url(r'^users/(?P[0-9]+)/$', + views.UserInstance.as_view(), + name='user-detail') + )) + + # Login and logout views for the browsable API + urlpatterns += patterns('', + url(r'^api-auth/', include('rest_framework.urls', + namespace='rest_framework')) + ) + +## Reviewing our work + +If we open a browser and navigate to the browseable API, you'll find that you can now work your way around the API simply by following links. + +You'll also be able to see the 'highlight' links on the snippet instances, that will take you to the hightlighted code HTML representations. + +We've now got a complete pastebin Web API, which is fully web browseable, 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's a few places you can start: + +* Contribute on [GitHub][github] by reviewing and subitting issues, and making pull requests. +* Join the [REST framework discussion group][group], and help build the community. +* Follow the author [on Twitter][twitter] and say hi. + +**Now go build some awesome things.** + +[repo]: https://github.com/tomchristie/rest-framework-tutorial +[sandbox]: http://sultry-coast-6726.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 \ No newline at end of file -- cgit v1.2.3 From db635fa6327d8d3ac3b06886c5f459b5c5a5cd04 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sun, 28 Oct 2012 19:37:27 +0000 Subject: Minor fixes --- docs/index.md | 6 ++---- docs/template.html | 3 +-- docs/tutorial/1-serialization.md | 6 +++--- docs/tutorial/2-requests-and-responses.md | 8 ++++---- docs/tutorial/4-authentication-and-permissions.md | 2 ++ 5 files changed, 12 insertions(+), 13 deletions(-) (limited to 'docs') diff --git a/docs/index.md b/docs/index.md index f66ba7f4..bd0c007d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -67,9 +67,8 @@ The tutorial will walk you through the building blocks that make up REST framewo * [1 - Serialization][tut-1] * [2 - Requests & Responses][tut-2] * [3 - Class based views][tut-3] -* [4 - Authentication, permissions & throttling][tut-4] +* [4 - Authentication & permissions][tut-4] * [5 - Relationships & hyperlinked APIs][tut-5] - ## API Guide @@ -161,9 +160,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [tut-1]: tutorial/1-serialization.md [tut-2]: tutorial/2-requests-and-responses.md [tut-3]: tutorial/3-class-based-views.md -[tut-4]: tutorial/4-authentication-permissions-and-throttling.md +[tut-4]: tutorial/4-authentication-and-permissions.md [tut-5]: tutorial/5-relationships-and-hyperlinked-apis.md -[tut-6]: tutorial/6-resource-orientated-projects.md [request]: api-guide/requests.md [response]: api-guide/responses.md diff --git a/docs/template.html b/docs/template.html index 56c5d832..557b45b3 100644 --- a/docs/template.html +++ b/docs/template.html @@ -57,9 +57,8 @@
  • 1 - Serialization
  • 2 - Requests and responses
  • 3 - Class based views
  • -
  • 4 - Authentication, permissions and throttling
  • +
  • 4 - Authentication and permissions
  • 5 - Relationships and hyperlinked APIs
  • -