diff --git a/.travis.yml b/.travis.yml index c9d9a1648..100a7cd8b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,6 @@ env: - TOX_ENV=py35-django18 - TOX_ENV=py34-django18 - TOX_ENV=py33-django18 - - TOX_ENV=py32-django18 - TOX_ENV=py27-django18 - TOX_ENV=py27-django110 - TOX_ENV=py35-django110 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 03865b755..415e42ac0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,6 +61,7 @@ To run the tests, clone the repository, and then: # Setup the virtual environment virtualenv env source env/bin/activate + pip install django pip install -r requirements.txt # Run the tests diff --git a/README.md b/README.md index fc9bd43a5..155f8dead 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,16 @@ REST framework commercially we strongly encourage you to invest in its continued development by **[signing up for a paid plan][funding]**. The initial aim is to provide a single full-time position on REST framework. -Right now we're a little over 45% of the way towards achieving that. -*Every single sign-up makes a significant impact.* Taking out a -[basic tier sponsorship](https://fund.django-rest-framework.org/topics/funding/#corporate-plans) moves us about 1% closer to our funding target. +*Every single sign-up makes a significant impact towards making that possible.*
-*Many thanks to all our [awesome sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/) and [Sentry](https://getsentry.com/welcome/).* +*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), and [Machinalis](http://www.machinalis.com/#services).* --- @@ -52,7 +52,7 @@ There is a live example API for testing purposes, [available here][sandbox]. # Requirements * Python (2.7, 3.2, 3.3, 3.4, 3.5) -* Django (1.8, 1.9) +* Django (1.8, 1.9, 1.10) # Installation @@ -170,7 +170,7 @@ You may also want to [follow the author on Twitter][twitter]. # Security -If you believe you’ve found something in Django REST framework which has security implications, please **do not raise the issue in a public forum**. +If you believe you've found something in Django REST framework which has security implications, please **do not raise the issue in a public forum**. Send a description of the issue via email to [rest-framework-security@googlegroups.com][security-mail]. The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure. diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 3f981c033..bf3a31eb7 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -44,7 +44,7 @@ The default authentication schemes may be set globally, using the `DEFAULT_AUTHE } You can also set the authentication scheme on a per-view or per-viewset basis, -using the `APIView` class based views. +using the `APIView` class-based views. from rest_framework.authentication import SessionAuthentication, BasicAuthentication from rest_framework.permissions import IsAuthenticated @@ -148,7 +148,7 @@ For clients to authenticate, the token key should be included in the `Authorizat If successfully authenticated, `TokenAuthentication` provides the following credentials. * `request.user` will be a Django `User` instance. -* `request.auth` will be a `rest_framework.authtoken.models.BasicToken` instance. +* `request.auth` will be a `rest_framework.authtoken.models.Token` instance. Unauthenticated responses that are denied permission will result in an `HTTP 401 Unauthorized` response with an appropriate WWW-Authenticate header. For example: diff --git a/docs/api-guide/content-negotiation.md b/docs/api-guide/content-negotiation.md index bc3b09fb7..bd408feba 100644 --- a/docs/api-guide/content-negotiation.md +++ b/docs/api-guide/content-negotiation.md @@ -77,7 +77,7 @@ The default content negotiation class may be set globally, using the `DEFAULT_CO 'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'myapp.negotiation.IgnoreClientContentNegotiation', } -You can also set the content negotiation used for an individual view, or viewset, using the `APIView` class based views. +You can also set the content negotiation used for an individual view, or viewset, using the `APIView` class-based views. from myapp.negotiation import IgnoreClientContentNegotiation from rest_framework.response import Response diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index 3e4b3e8be..df8cad42d 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -98,7 +98,7 @@ Note that the exception handler will only be called for responses generated by r The **base class** for all exceptions raised inside an `APIView` class or `@api_view`. -To provide a custom exception, subclass `APIException` and set the `.status_code` and `.default_detail` properties on the class. +To provide a custom exception, subclass `APIException` and set the `.status_code`, `.default_detail`, and `default_code` attributes on the class. For example, if your API relies on a third party service that may sometimes be unreachable, you might want to implement an exception for the "503 Service Unavailable" HTTP response code. You could do this like so: @@ -107,10 +107,42 @@ For example, if your API relies on a third party service that may sometimes be u class ServiceUnavailable(APIException): status_code = 503 default_detail = 'Service temporarily unavailable, try again later.' + default_code = 'service_unavailable' + +#### Inspecting API exceptions + +There are a number of different properties available for inspecting the status +of an API exception. You can use these to build custom exception handling +for your project. + +The available attributes and methods are: + +* `.detail` - Return the textual description of the error. +* `.get_codes()` - Return the code identifier of the error. +* `.get_full_details()` - Return both the textual description and the code identifier. + +In most cases the error detail will be a simple item: + + >>> print(exc.detail) + You do not have permission to perform this action. + >>> print(exc.get_codes()) + permission_denied + >>> print(exc.get_full_details()) + {'message':'You do not have permission to perform this action.','code':'permission_denied'} + +In the case of validation errors the error detail will be either a list or +dictionary of items: + + >>> print(exc.detail) + {"name":"This field is required.","age":"A valid integer is required."} + >>> print(exc.get_codes()) + {"name":"required","age":"invalid"} + >>> print(exc.get_full_details()) + {"name":{"message":"This field is required.","code":"required"},"age":{"message":"A valid integer is required.","code":"invalid"}} ## ParseError -**Signature:** `ParseError(detail=None)` +**Signature:** `ParseError(detail=None, code=None)` Raised if the request contains malformed data when accessing `request.data`. @@ -118,7 +150,7 @@ By default this exception results in a response with the HTTP status code "400 B ## AuthenticationFailed -**Signature:** `AuthenticationFailed(detail=None)` +**Signature:** `AuthenticationFailed(detail=None, code=None)` Raised when an incoming request includes incorrect authentication. @@ -126,7 +158,7 @@ By default this exception results in a response with the HTTP status code "401 U ## NotAuthenticated -**Signature:** `NotAuthenticated(detail=None)` +**Signature:** `NotAuthenticated(detail=None, code=None)` Raised when an unauthenticated request fails the permission checks. @@ -134,7 +166,7 @@ By default this exception results in a response with the HTTP status code "401 U ## PermissionDenied -**Signature:** `PermissionDenied(detail=None)` +**Signature:** `PermissionDenied(detail=None, code=None)` Raised when an authenticated request fails the permission checks. @@ -142,7 +174,7 @@ By default this exception results in a response with the HTTP status code "403 F ## NotFound -**Signature:** `NotFound(detail=None)` +**Signature:** `NotFound(detail=None, code=None)` Raised when a resource does not exists at the given URL. This exception is equivalent to the standard `Http404` Django exception. @@ -150,7 +182,7 @@ By default this exception results in a response with the HTTP status code "404 N ## MethodNotAllowed -**Signature:** `MethodNotAllowed(method, detail=None)` +**Signature:** `MethodNotAllowed(method, detail=None, code=None)` Raised when an incoming request occurs that does not map to a handler method on the view. @@ -158,7 +190,7 @@ By default this exception results in a response with the HTTP status code "405 M ## NotAcceptable -**Signature:** `NotAcceptable(detail=None)` +**Signature:** `NotAcceptable(detail=None, code=None)` Raised when an incoming request occurs with an `Accept` header that cannot be satisfied by any of the available renderers. @@ -166,7 +198,7 @@ By default this exception results in a response with the HTTP status code "406 N ## UnsupportedMediaType -**Signature:** `UnsupportedMediaType(media_type, detail=None)` +**Signature:** `UnsupportedMediaType(media_type, detail=None, code=None)` Raised if there are no parsers that can handle the content type of the request data when accessing `request.data`. @@ -174,7 +206,7 @@ By default this exception results in a response with the HTTP status code "415 U ## Throttled -**Signature:** `Throttled(wait=None, detail=None)` +**Signature:** `Throttled(wait=None, detail=None, code=None)` Raised when an incoming request fails the throttling checks. @@ -182,7 +214,7 @@ By default this exception results in a response with the HTTP status code "429 T ## ValidationError -**Signature:** `ValidationError(detail)` +**Signature:** `ValidationError(detail, code=None)` The `ValidationError` exception is slightly different from the other `APIException` classes: diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index b58d02898..17168b721 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -49,7 +49,9 @@ Defaults to `False` ### `default` -If set, this gives the default value that will be used for the field if no input value is supplied. If not set the default behavior is to not populate the attribute at all. +If set, this gives the default value that will be used for the field if no input value is supplied. If not set the default behaviour is to not populate the attribute at all. + +The `default` is not applied during partial update operations. In the partial update case only fields that are provided in the incoming data will have a validated value returned. May be set to a function or other callable, in which case the value will be evaluated each time it is used. When called, it will receive no arguments. If the callable has a `set_context` method, that will be called each time before getting the value with the field instance as only argument. This works the same way as for [validators](validators.md#using-set_context). @@ -261,9 +263,10 @@ Corresponds to `django.db.models.fields.DecimalField`. - `max_digits` The maximum number of digits allowed in the number. Note that this number must be greater than or equal to decimal_places. - `decimal_places` The number of decimal places to store with the number. -- `coerce_to_string` Set to `True` if string values should be returned for the representation, or `False` if `Decimal` objects should be returned. Defaults to the same value as the `COERCE_DECIMAL_TO_STRING` settings key, which will be `True` unless overridden. If `Decimal` objects are returned by the serializer, then the final output format will be determined by the renderer. +- `coerce_to_string` Set to `True` if string values should be returned for the representation, or `False` if `Decimal` objects should be returned. Defaults to the same value as the `COERCE_DECIMAL_TO_STRING` settings key, which will be `True` unless overridden. If `Decimal` objects are returned by the serializer, then the final output format will be determined by the renderer. Note that setting `localize` will force the value to `True`. - `max_value` Validate that the number provided is no greater than this value. - `min_value` Validate that the number provided is no less than this value. +- `localize` Set to `True` to enable localization of input and output based on the current locale. This will also force `coerce_to_string` to `True`. Defaults to `False`. Note that data formatting is enabled if you have set `USE_L10N=True` in your settings file. #### Example usage @@ -289,9 +292,9 @@ A date and time representation. Corresponds to `django.db.models.fields.DateTimeField`. -**Signature:** `DateTimeField(format=None, input_formats=None)` +**Signature:** `DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)` -* `format` - A string representing the output format. If not specified, this defaults to the same value as the `DATETIME_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `datetime` objects should be returned by `to_representation`. In this case the datetime encoding will be determined by the renderer. +* `format` - A string representing the output format. If not specified, this defaults to the same value as the `DATETIME_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `datetime` objects should be returned by `to_representation`. In this case the datetime encoding will be determined by the renderer. * `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `DATETIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`. #### `DateTimeField` format strings. @@ -320,7 +323,7 @@ A date representation. Corresponds to `django.db.models.fields.DateField` -**Signature:** `DateField(format=None, input_formats=None)` +**Signature:** `DateField(format=api_settings.DATE_FORMAT, input_formats=None)` * `format` - A string representing the output format. If not specified, this defaults to the same value as the `DATE_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `date` objects should be returned by `to_representation`. In this case the date encoding will be determined by the renderer. * `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `DATE_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`. @@ -335,7 +338,7 @@ A time representation. Corresponds to `django.db.models.fields.TimeField` -**Signature:** `TimeField(format=None, input_formats=None)` +**Signature:** `TimeField(format=api_settings.TIME_FORMAT, input_formats=None)` * `format` - A string representing the output format. If not specified, this defaults to the same value as the `TIME_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `time` objects should be returned by `to_representation`. In this case the time encoding will be determined by the renderer. * `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `TIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`. @@ -485,7 +488,7 @@ This field is used by default with `ModelSerializer` when including field names **Signature**: `ReadOnlyField()` -For example, is `has_expired` was a property on the `Account` model, then the following serializer would automatically generate it as a `ReadOnlyField`: +For example, if `has_expired` was a property on the `Account` model, then the following serializer would automatically generate it as a `ReadOnlyField`: class AccountSerializer(serializers.ModelSerializer): class Meta: @@ -623,7 +626,6 @@ The `.fail()` method is a shortcut for raising `ValidationError` that takes a me def to_internal_value(self, data): if not isinstance(data, six.text_type): - msg = 'Incorrect type. Expected a string, but got %s' self.fail('incorrect_type', input_type=type(data).__name__) if not re.match(r'^rgb\([0-9]+,[0-9]+,[0-9]+\)$', data): diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index fcab15a79..1b49d3a73 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -89,24 +89,24 @@ Generic filters can also present themselves as HTML controls in the browsable AP ## Setting filter backends -The default filter backends may be set globally, using the `DEFAULT_FILTER_BACKENDS` setting. For example. +The default filter backends may be set globally, using the `DEFAULT_FILTER_BACKENDS` setting. For example. REST_FRAMEWORK = { - 'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',) + 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',) } You can also set the filter backends on a per-view, or per-viewset basis, -using the `GenericAPIView` class based views. +using the `GenericAPIView` class-based views. + import django_filters from django.contrib.auth.models import User from myapp.serializers import UserSerializer - from rest_framework import filters from rest_framework import generics class UserListView(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer - filter_backends = (filters.DjangoFilterBackend,) + filter_backends = (django_filters.rest_framework.DjangoFilterBackend,) ## Filtering and object lookups @@ -139,12 +139,27 @@ Note that you can use both an overridden `.get_queryset()` and generic filtering ## DjangoFilterBackend -The `DjangoFilterBackend` class supports highly customizable field filtering, using the [django-filter package][django-filter]. +The `django-filter` library includes a `DjangoFilterBackend` class which +supports highly customizable field filtering for REST framework. -To use REST framework's `DjangoFilterBackend`, first install `django-filter`. +To use `DjangoFilterBackend`, first install `django-filter`. pip install django-filter +You should now either add the filter backend to your settings: + + REST_FRAMEWORK = { + 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',) + } + +Or add the filter backend to an individual View or ViewSet. + + from django_filters.rest_framework import DjangoFilterBackend + + class UserListView(generics.ListAPIView): + ... + filter_backends = (DjangoFilterBackend,) + If you are using the browsable API or admin API you may also want to install `django-crispy-forms`, which will enhance the presentation of the filter forms in HTML views, by allowing them to render Bootstrap 3 HTML. pip install django-crispy-forms @@ -174,12 +189,11 @@ For more advanced filtering requirements you can specify a `FilterSet` class tha import django_filters from myapp.models import Product from myapp.serializers import ProductSerializer - from rest_framework import filters from rest_framework import generics - class ProductFilter(filters.FilterSet): - min_price = django_filters.NumberFilter(name="price", lookup_type='gte') - max_price = django_filters.NumberFilter(name="price", lookup_type='lte') + class ProductFilter(django_filters.rest_framework.FilterSet): + min_price = django_filters.NumberFilter(name="price", lookup_expr='gte') + max_price = django_filters.NumberFilter(name="price", lookup_expr='lte') class Meta: model = Product fields = ['category', 'in_stock', 'min_price', 'max_price'] @@ -187,7 +201,7 @@ For more advanced filtering requirements you can specify a `FilterSet` class tha class ProductList(generics.ListAPIView): queryset = Product.objects.all() serializer_class = ProductSerializer - filter_backends = (filters.DjangoFilterBackend,) + filter_backends = (django_filters.rest_framework.DjangoFilterBackend,) filter_class = ProductFilter @@ -199,12 +213,12 @@ You can also span relationships using `django-filter`, let's assume that each product has foreign key to `Manufacturer` model, so we create filter that filters using `Manufacturer` name. For example: + import django_filters from myapp.models import Product from myapp.serializers import ProductSerializer - from rest_framework import filters from rest_framework import generics - class ProductFilter(filters.FilterSet): + class ProductFilter(django_filters.rest_framework.FilterSet): class Meta: model = Product fields = ['category', 'in_stock', 'manufacturer__name'] @@ -218,10 +232,9 @@ This is nice, but it exposes the Django's double underscore convention as part o import django_filters from myapp.models import Product from myapp.serializers import ProductSerializer - from rest_framework import filters from rest_framework import generics - class ProductFilter(filters.FilterSet): + class ProductFilter(django_filters.rest_framework.FilterSet): manufacturer = django_filters.CharFilter(name="manufacturer__name") class Meta: @@ -241,7 +254,6 @@ For more details on using filter sets see the [django-filter documentation][djan * By default filtering is not enabled. If you want to use `DjangoFilterBackend` remember to make sure it is installed by using the `'DEFAULT_FILTER_BACKENDS'` setting. * When using boolean fields, you should use the values `True` and `False` in the URL query parameters, rather than `0`, `1`, `true` or `false`. (The allowed boolean values are currently hardwired in Django's [NullBooleanSelect implementation][nullbooleanselect].) * `django-filter` supports filtering across relationships, using Django's double-underscore syntax. -* For Django 1.3 support, make sure to install `django-filter` version 0.5.4, as later versions drop support for 1.3. --- @@ -417,6 +429,12 @@ Generic filters may also present an interface in the browsable API. To do so you The method should return a rendered HTML string. +## Pagination & schemas + +You can also make the filter controls available to the schema autogeneration +that REST framework provides, by implementing a `get_schema_fields()` method, +which should return a list of `coreapi.Field` instances. + # Third party packages The following third party packages provide additional filter implementations. @@ -433,6 +451,10 @@ The [djangorestframework-word-filter][django-rest-framework-word-search-filter] [django-url-filter][django-url-filter] provides a safe way to filter data via human-friendly URLs. It works very similar to DRF serializers and fields in a sense that they can be nested except they are called filtersets and filters. That provides easy way to filter related data. Also this library is generic-purpose so it can be used to filter other sources of data and not only Django `QuerySet`s. +## drf-url-filters + +[drf-url-filter][drf-url-filter] is a simple Django app to apply filters on drf `ModelViewSet`'s `Queryset` in a clean, simple and configurable way. It also supports validations on incoming query params and their values. A beautiful python package `Voluptuous` is being used for validations on the incoming query parameters. The best part about voluptuous is you can define your own validations as per your query params requirements. + [cite]: https://docs.djangoproject.com/en/dev/topics/db/queries/#retrieving-specific-objects-with-filters [django-filter]: https://github.com/alex/django-filter [django-filter-docs]: https://django-filter.readthedocs.io/en/latest/index.html @@ -444,3 +466,4 @@ The [djangorestframework-word-filter][django-rest-framework-word-search-filter] [django-rest-framework-filters]: https://github.com/philipn/django-rest-framework-filters [django-rest-framework-word-search-filter]: https://github.com/trollknurr/django-rest-framework-word-search-filter [django-url-filter]: https://github.com/miki725/django-url-filter +[drf-url-filter]: https://github.com/manjitkumar/drf-url-filters diff --git a/docs/api-guide/format-suffixes.md b/docs/api-guide/format-suffixes.md index 13717b05f..05dde47f2 100644 --- a/docs/api-guide/format-suffixes.md +++ b/docs/api-guide/format-suffixes.md @@ -42,7 +42,7 @@ When using `format_suffix_patterns`, you must make sure to add the `'format'` ke def comment_list(request, format=None): # do stuff... -Or with class based views: +Or with class-based views: class CommentList(APIView): def get(self, request, format=None): diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index e6e300561..c368d0b46 100644 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -7,7 +7,7 @@ source: mixins.py > > — [Django Documentation][cite] -One of the key benefits of class based views is the way they allow you to compose bits of reusable behavior. REST framework takes advantage of this by providing a number of pre-built views that provide for commonly used patterns. +One of the key benefits of class-based views is the way they allow you to compose bits of reusable behavior. REST framework takes advantage of this by providing a number of pre-built views that provide for commonly used patterns. The generic views provided by REST framework allow you to quickly build API views that map closely to your database models. @@ -220,8 +220,6 @@ Also provides a `.partial_update(request, *args, **kwargs)` method, which is sim If an object is updated this returns a `200 OK` response, with a serialized representation of the object as the body of the response. -If an object is created, for example when making a `DELETE` request followed by a `PUT` request to the same URL, this returns a `201 Created` response, with a serialized representation of the object as the body of the response. - If the request data provided for updating the object was invalid, a `400 Bad Request` response will be returned, with the error details as the body of the response. ## DestroyModelMixin @@ -330,7 +328,8 @@ For example, if you need to lookup objects based on multiple fields in the URL c queryset = self.filter_queryset(queryset) # Apply any filter backends filter = {} for field in self.lookup_fields: - filter[field] = self.kwargs[field] + if self.kwargs[field]: # Ignore empty fields. + filter[field] = self.kwargs[field] return get_object_or_404(queryset, **filter) # Lookup the object You can then simply apply this mixin to a view or viewset anytime you need to apply the custom behavior. diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index 0dd935ba3..f82614eca 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -21,12 +21,15 @@ Pagination can be turned off by setting the pagination class to `None`. ## Setting the pagination style -The default pagination style may be set globally, using the `DEFAULT_PAGINATION_CLASS` settings key. For example, to use the built-in limit/offset pagination, you would do: +The default pagination style may be set globally, using the `DEFAULT_PAGINATION_CLASS` and `PAGE_SIZE` setting keys. For example, to use the built-in limit/offset pagination, you would do something like this: REST_FRAMEWORK = { - 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination' + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', + 'PAGE_SIZE': 100 } +Note that you need to set both the pagination class, and the page size that should be used. + You can also set the pagination class on an individual view by using the `pagination_class` attribute. Typically you'll want to use the same pagination style throughout your API, although you might want to vary individual aspects of the pagination, such as default or maximum page size, on a per-view basis. ## Modifying the pagination style @@ -109,7 +112,7 @@ To set these attributes you should override the `PageNumberPagination` class, an ## LimitOffsetPagination -This pagination style mirrors the syntax used when looking up multiple database records. The client includes both a "limit" and an +This pagination style mirrors the syntax used when looking up multiple database records. The client includes both a "limit" and an "offset" query parameter. The limit indicates the maximum number of items to return, and is equivalent to the `page_size` in other styles. The offset indicates the starting position of the query in relation to the complete set of unpaginated items. **Request**: @@ -273,6 +276,12 @@ To have your custom pagination class be used by default, use the `DEFAULT_PAGINA API responses for list endpoints will now include a `Link` header, instead of including the pagination links as part of the body of the response, for example: +## Pagination & schemas + +You can also make the pagination controls available to the schema autogeneration +that REST framework provides, by implementing a `get_schema_fields()` method, +which should return a list of `coreapi.Field` instances. + --- ![Link Header][link-header] @@ -312,9 +321,14 @@ The following third party packages are also available. The [`DRF-extensions` package][drf-extensions] includes a [`PaginateByMaxMixin` mixin class][paginate-by-max-mixin] that allows your API clients to specify `?page_size=max` to obtain the maximum allowed page size. +## drf-proxy-pagination + +The [`drf-proxy-pagination` package][drf-proxy-pagination] includes a `ProxyPagination` class which allows to choose pagination class with a query parameter. + [cite]: https://docs.djangoproject.com/en/dev/topics/pagination/ [github-link-pagination]: https://developer.github.com/guides/traversing-with-pagination/ [link-header]: ../img/link-header-pagination.png [drf-extensions]: http://chibisov.github.io/drf-extensions/docs/ [paginate-by-max-mixin]: http://chibisov.github.io/drf-extensions/docs/#paginatebymaxmixin +[drf-proxy-pagination]: https://github.com/tuffnatty/drf-proxy-pagination [disqus-cursor-api]: http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index e1e6d1d65..ef2859fe1 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -35,7 +35,7 @@ The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSE } You can also set the parsers used for an individual view, or viewset, -using the `APIView` class based views. +using the `APIView` class-based views. from rest_framework.parsers import JSONParser from rest_framework.response import Response diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 5386e4df6..7cdb59531 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -71,7 +71,7 @@ If not specified, this setting defaults to allowing unrestricted access: ) You can also set the authentication policy on a per-view, or per-viewset basis, -using the `APIView` class based views. +using the `APIView` class-based views. from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @@ -92,7 +92,7 @@ Or, if you're using the `@api_view` decorator with function based views. from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response - @api_view('GET') + @api_view(['GET']) @permission_classes((IsAuthenticated, )) def example_view(request, format=None): content = { @@ -132,7 +132,7 @@ This permission is suitable if you want to your API to allow read permissions to ## DjangoModelPermissions -This permission class ties into Django's standard `django.contrib.auth` [model permissions][contribauth]. This permission must only be applied to views that has a `.queryset` property set. Authorization will only be granted if the user *is authenticated* and has the *relevant model permissions* assigned. +This permission class ties into Django's standard `django.contrib.auth` [model permissions][contribauth]. This permission must only be applied to views that have a `.queryset` property set. Authorization will only be granted if the user *is authenticated* and has the *relevant model permissions* assigned. * `POST` requests require the user to have the `add` permission on the model. * `PUT` and `PATCH` requests require the user to have the `change` permission on the model. @@ -261,6 +261,10 @@ The [REST Condition][rest-condition] package is another extension for building c The [DRY Rest Permissions][dry-rest-permissions] package provides the ability to define different permissions for individual default and custom actions. This package is made for apps with permissions that are derived from relationships defined in the app's data model. It also supports permission checks being returned to a client app through the API's serializer. Additionally it supports adding permissions to the default and custom list actions to restrict the data they retrive per user. +## Django Rest Framework Roles + +The [Django Rest Framework Roles][django-rest-framework-roles] package makes it easier to parameterize your API over multiple types of users. + [cite]: https://developer.apple.com/library/mac/#documentation/security/Conceptual/AuthenticationAndAuthorizationGuide/Authorization/Authorization.html [authentication]: authentication.md [throttling]: throttling.md @@ -275,3 +279,4 @@ The [DRY Rest Permissions][dry-rest-permissions] package provides the ability to [composed-permissions]: https://github.com/niwibe/djangorestframework-composed-permissions [rest-condition]: https://github.com/caxap/rest_condition [dry-rest-permissions]: https://github.com/Helioscene/dry-rest-permissions +[django-rest-framework-roles]: https://github.com/computer-lab/django-rest-framework-roles diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index 8695b2c1e..aabe49412 100644 --- a/docs/api-guide/relations.md +++ b/docs/api-guide/relations.md @@ -39,7 +39,7 @@ In order to explain the various types of relational fields, we'll use a couple o artist = models.CharField(max_length=100) class Track(models.Model): - album = models.ForeignKey(Album, related_name='tracks') + album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE) order = models.IntegerField() title = models.CharField(max_length=100) duration = models.IntegerField() @@ -99,8 +99,8 @@ For example, the following serializer: Would serialize to a representation like this: { - 'album_name': 'The Roots', - 'artist': 'Undun', + 'album_name': 'Undun', + 'artist': 'The Roots', 'tracks': [ 89, 90, @@ -286,7 +286,7 @@ Would serialize to a nested representation like this: ], } -# Writable nested serializers +## Writable nested serializers By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create `create()` and/or `update()` methods in order to explicitly specify how the child relationships should be saved. @@ -324,8 +324,14 @@ By default nested serializers are read-only. If you want to support write-operat >>> serializer.save()