diff --git a/.gitignore b/.gitignore index e9222c2da..41768084c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ /*.egg-info/ /env/ MANIFEST +coverage.* !.gitignore !.travis.yml diff --git a/.travis.yml b/.travis.yml index cf3cddfec..cd87dd339 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,31 +1,51 @@ language: python python: - - "3.5" + - "2.7" + - "3.4" + - "3.5" sudo: false env: - - TOX_ENV=py27-lint - - TOX_ENV=py27-docs - - TOX_ENV=py35-django19 - - TOX_ENV=py34-django19 - - TOX_ENV=py27-django19 - - TOX_ENV=py34-django18 - - TOX_ENV=py33-django18 - - TOX_ENV=py32-django18 - - TOX_ENV=py27-django18 + - DJANGO=1.8 + - DJANGO=1.9 + - DJANGO=1.10 + - DJANGO=1.11 + - DJANGO=master matrix: - fast_finish: true + fast_finish: true + include: + - python: "3.6" + env: DJANGO=master + - python: "3.6" + env: DJANGO=1.11 + - python: "3.3" + env: DJANGO=1.8 + - python: "2.7" + env: TOXENV="lint" + - python: "2.7" + env: TOXENV="docs" + exclude: + - python: "2.7" + env: DJANGO=master + - python: "3.4" + env: DJANGO=master + + allow_failures: + - env: DJANGO=master + - env: DJANGO=1.11 install: - # Virtualenv < 14 is required to keep the Python 3.2 builds running. - - pip install tox "virtualenv<14" + - pip install tox tox-travis script: - - tox -e $TOX_ENV + - tox after_success: - pip install codecov - - codecov -e TOX_ENV + - codecov -e TOXENV,DJANGO + +notifications: + email: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 99479acdf..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 @@ -200,7 +201,7 @@ If you want to draw attention to a note or warning, use a pair of enclosing line [issues]: https://github.com/tomchristie/django-rest-framework/issues?state=open [pep-8]: http://www.python.org/dev/peps/pep-0008/ [pull-requests]: https://help.github.com/articles/using-pull-requests -[tox]: http://tox.readthedocs.org/en/latest/ +[tox]: https://tox.readthedocs.io/en/latest/ [markdown]: http://daringfireball.net/projects/markdown/basics [docs]: https://github.com/tomchristie/django-rest-framework/tree/master/docs [mou]: http://mouapp.com/ diff --git a/README.md b/README.md index 9025e91b4..609f99184 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![build-status-image]][travis] [![coverage-status-image]][codecov] [![pypi-version]][pypi] +[](https://gitter.im/tomchristie/django-rest-framework?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) **Awesome web-browsable Web APIs.** @@ -10,9 +11,24 @@ Full documentation for the project is available at [http://www.django-rest-frame --- -**Note**: We have now released Django REST framework 3.3. For older codebases you may want to refer to the version 2.4.4 [source code][2.4-code], and [documentation][2.4-docs]. +# Funding -For more details see the 3.3 [announcement][3.3-announcement] and [release notes][3.3-release-notes]. +REST framework is a *collaboratively funded project*. If you use +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. +*Every single sign-up makes a significant impact towards making that possible.* + +
+ +*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), [Machinalis](https://hello.machinalis.co.uk/), and [Rollbar](https://rollbar.com).* --- @@ -37,7 +53,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 @@ -155,7 +171,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. @@ -169,6 +185,9 @@ Send a description of the issue via email to [rest-framework-security@googlegrou [group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework [sandbox]: http://restframework.herokuapp.com/ +[funding]: https://fund.django-rest-framework.org/topics/funding/ +[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors + [oauth1-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth [oauth2-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit [serializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#serializers @@ -183,7 +202,3 @@ Send a description of the issue via email to [rest-framework-security@googlegrou [docs]: http://www.django-rest-framework.org/ [security-mail]: mailto:rest-framework-security@googlegroups.com -[2.4-code]: https://github.com/tomchristie/django-rest-framework/tree/version-2.4.x -[2.4-docs]: http://tomchristie.github.io/rest-framework-2-docs/ -[3.3-announcement]: http://www.django-rest-framework.org/topics/3.3-announcement/ -[3.3-release-notes]: http://www.django-rest-framework.org/topics/release-notes/#33x-series diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 3d1cfd1e8..4a01188f3 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 @@ -128,11 +128,10 @@ To use the `TokenAuthentication` scheme you'll need to [configure the authentica --- -**Note:** Make sure to run `manage.py syncdb` after changing your settings. The `rest_framework.authtoken` app provides both Django (from v1.7) and South database migrations. See [Schema migrations](#schema-migrations) below. +**Note:** Make sure to run `manage.py migrate` after changing your settings. The `rest_framework.authtoken` app provides Django database migrations. --- - You'll also need to create tokens for your users. from rest_framework.authtoken.models import Token @@ -144,10 +143,12 @@ For clients to authenticate, the token key should be included in the `Authorizat Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b +**Note:** If you want to use a different keyword in the header, such as `Bearer`, simply subclass `TokenAuthentication` and set the `keyword` class variable. + 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: @@ -206,6 +207,10 @@ The `obtain_auth_token` view will return a JSON response when valid `username` a Note that the default `obtain_auth_token` view explicitly uses JSON requests and responses, rather than using default renderer and parser classes in your settings. If you need a customized version of the `obtain_auth_token` view, you can do so by overriding the `ObtainAuthToken` view class, and using that in your url conf instead. +By default there are no permissions or throttling applied to the `obtain_auth_token` view. If you do wish to apply throttling you'll need to override the view class, +and include them using the `throttle_classes` attribute. + + ##### With Django admin It is also possible to create Tokens manually through admin interface. In case you are using a large user base, we recommend that you monkey patch the `TokenAdmin` class to customize it to your needs, more specifically by declaring the `user` field as `raw_field`. @@ -217,38 +222,6 @@ It is also possible to create Tokens manually through admin interface. In case y TokenAdmin.raw_id_fields = ('user',) -#### Schema migrations - -The `rest_framework.authtoken` app includes both Django native migrations (for Django versions >1.7) and South migrations (for Django versions <1.7) that will create the authtoken table. - ----- - -**Note**: From REST Framework v2.4.0 using South with Django <1.7 requires upgrading South v1.0+ - ----- - - -If you're using a [custom user model][custom-user-model] you'll need to make sure that any initial migration that creates the user table runs before the authtoken table is created. - -You can do so by inserting a `needed_by` attribute in your user migration: - - class Migration: - - needed_by = ( - ('authtoken', '0001_initial'), - ) - - def forwards(self): - ... - -For more details, see the [south documentation on dependencies][south-dependencies]. - -Also note that if you're using a `post_save` signal to create tokens, then the first time you create the database tables, you'll need to ensure any migrations are run prior to creating any superusers. For example: - - python manage.py syncdb --noinput # Won't create a superuser just yet, due to `--noinput`. - python manage.py migrate - python manage.py createsuperuser - ## SessionAuthentication This authentication scheme uses Django's default session backend for authentication. Session authentication is appropriate for AJAX clients that are running in the same session context as your website. @@ -390,11 +363,9 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a [oauth]: http://oauth.net/2/ [permission]: permissions.md [throttling]: throttling.md -[csrf-ajax]: https://docs.djangoproject.com/en/dev/ref/csrf/#ajax +[csrf-ajax]: https://docs.djangoproject.com/en/stable/ref/csrf/#ajax [mod_wsgi_official]: http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIPassAuthorization -[custom-user-model]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#specifying-a-custom-user-model -[south-dependencies]: http://south.readthedocs.org/en/latest/dependencies.html -[django-oauth-toolkit-getting-started]: https://django-oauth-toolkit.readthedocs.org/en/latest/rest-framework/getting_started.html +[django-oauth-toolkit-getting-started]: https://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html [django-rest-framework-oauth]: http://jpadilla.github.io/django-rest-framework-oauth/ [django-rest-framework-oauth-authentication]: http://jpadilla.github.io/django-rest-framework-oauth/authentication/ [django-rest-framework-oauth-permissions]: http://jpadilla.github.io/django-rest-framework-oauth/permissions/ @@ -403,7 +374,7 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a [oauth-1.0a]: http://oauth.net/core/1.0a [django-oauth-plus]: http://code.larlet.fr/django-oauth-plus [django-oauth2-provider]: https://github.com/caffeinehit/django-oauth2-provider -[django-oauth2-provider-docs]: https://django-oauth2-provider.readthedocs.org/en/latest/ +[django-oauth2-provider-docs]: https://django-oauth2-provider.readthedocs.io/en/latest/ [rfc6749]: http://tools.ietf.org/html/rfc6749 [django-oauth-toolkit]: https://github.com/evonove/django-oauth-toolkit [evonove]: https://github.com/evonove/ @@ -417,9 +388,9 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a [djangorestframework-httpsignature]: https://github.com/etoccalino/django-rest-framework-httpsignature [amazon-http-signature]: http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html [http-signature-ietf-draft]: https://datatracker.ietf.org/doc/draft-cavage-http-signatures/ -[hawkrest]: http://hawkrest.readthedocs.org/en/latest/ +[hawkrest]: https://hawkrest.readthedocs.io/en/latest/ [hawk]: https://github.com/hueniverse/hawk -[mohawk]: http://mohawk.readthedocs.org/en/latest/ +[mohawk]: https://mohawk.readthedocs.io/en/latest/ [mac]: http://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-05 [djoser]: https://github.com/sunscrapers/djoser [django-rest-auth]: https://github.com/Tivix/django-rest-auth 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..03f16222d 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -47,7 +47,7 @@ Any example validation error might look like this: You can implement custom exception handling by creating a handler function that converts exceptions raised in your API views into response objects. This allows you to control the style of error responses used by your API. -The function must take a pair of arguments, this first is the exception to be handled, and the second is a dictionary containing any extra context such as the view currently being handled. The exception handler function should either return a `Response` object, or return `None` if the exception cannot be handled. If the handler returns `None` then the exception will be re-raised and Django will return a standard HTTP 500 'server error' response. +The function must take a pair of arguments, the first is the exception to be handled, and the second is a dictionary containing any extra context such as the view currently being handled. The exception handler function should either return a `Response` object, or return `None` if the exception cannot be handled. If the handler returns `None` then the exception will be re-raised and Django will return a standard HTTP 500 'server error' response. For example, you might want to ensure that all error responses include the HTTP status code in the body of the response, like so: @@ -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 0409f9a6c..22ce7dd77 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). @@ -259,11 +261,12 @@ Corresponds to `django.db.models.fields.DecimalField`. **Signature**: `DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None)` -- `max_digits` The maximum number of digits allowed in the number. Note that this number must be greater than or equal to decimal_places. +- `max_digits` The maximum number of digits allowed in the number. It must be either `None` or an integer 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']`. @@ -431,9 +434,11 @@ Requires either the `Pillow` package or `PIL` package. The `Pillow` package is A field class that validates a list of objects. -**Signature**: `ListField(child)` +**Signature**: `ListField(child, min_length=None, max_length=None)` - `child` - A field instance that should be used for validating the objects in the list. If this argument is not provided then objects in the list will not be validated. +- `min_length` - Validates that the list contains no fewer than this number of elements. +- `max_length` - Validates that the list contains no more than this number of elements. For example, to validate a list of integers you might use something like the following: @@ -485,7 +490,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 +628,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): @@ -663,14 +667,14 @@ The [django-rest-framework-gis][django-rest-framework-gis] package provides geog The [django-rest-framework-hstore][django-rest-framework-hstore] package provides an `HStoreField` to support [django-hstore][django-hstore] `DictionaryField` model field. -[cite]: https://docs.djangoproject.com/en/dev/ref/forms/api/#django.forms.Form.cleaned_data +[cite]: https://docs.djangoproject.com/en/stable/ref/forms/api/#django.forms.Form.cleaned_data [html-and-forms]: ../topics/html-and-forms.md -[FILE_UPLOAD_HANDLERS]: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FILE_UPLOAD_HANDLERS +[FILE_UPLOAD_HANDLERS]: https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-FILE_UPLOAD_HANDLERS [ecma262]: http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 -[strftime]: http://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior -[django-widgets]: https://docs.djangoproject.com/en/dev/ref/forms/widgets/ +[strftime]: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior +[django-widgets]: https://docs.djangoproject.com/en/stable/ref/forms/widgets/ [iso8601]: http://www.w3.org/TR/NOTE-datetime -[drf-compound-fields]: http://drf-compound-fields.readthedocs.org +[drf-compound-fields]: https://drf-compound-fields.readthedocs.io [drf-extra-fields]: https://github.com/Hipo/drf-extra-fields [djangorestframework-recursive]: https://github.com/heywbj/django-rest-framework-recursive [django-rest-framework-gis]: https://github.com/djangonauts/django-rest-framework-gis diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index c32300783..8a23a2ea3 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.rest_framework 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 = UserSerializer - filter_backends = (filters.DjangoFilterBackend,) + serializer_class = UserSerializer + 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. --- @@ -257,7 +269,7 @@ The `SearchFilter` class will only be applied if the view has a `search_fields` class UserListView(generics.ListAPIView): queryset = User.objects.all() - serializer = UserSerializer + serializer_class = UserSerializer filter_backends = (filters.SearchFilter,) search_fields = ('username', 'email') @@ -380,7 +392,7 @@ A complete example using both `DjangoObjectPermissionsFilter` and `DjangoObjectP 'change' or 'delete' permissions. """ queryset = Event.objects.all() - serializer = EventSerializer + serializer_class = EventSerializer filter_backends = (filters.DjangoObjectPermissionsFilter,) permission_classes = (myapp.permissions.CustomObjectPermissions,) @@ -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,14 +451,19 @@ 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. -[cite]: https://docs.djangoproject.com/en/dev/topics/db/queries/#retrieving-specific-objects-with-filters +## 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/stable/topics/db/queries/#retrieving-specific-objects-with-filters [django-filter]: https://github.com/alex/django-filter -[django-filter-docs]: https://django-filter.readthedocs.org/en/latest/index.html -[guardian]: https://django-guardian.readthedocs.org/ -[view-permissions]: https://django-guardian.readthedocs.org/en/latest/userguide/assign.html +[django-filter-docs]: https://django-filter.readthedocs.io/en/latest/index.html +[guardian]: https://django-guardian.readthedocs.io/ +[view-permissions]: https://django-guardian.readthedocs.io/en/latest/userguide/assign.html [view-permissions-blogpost]: http://blog.nyaruka.com/adding-a-view-permission-to-django-models [nullbooleanselect]: https://github.com/django/django/blob/master/django/forms/widgets.py -[search-django-admin]: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields +[search-django-admin]: https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields [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 4a9581212..606a3787a 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. @@ -26,7 +26,6 @@ Typically when using the generic views, you'll override the view, and set severa queryset = User.objects.all() serializer_class = UserSerializer permission_classes = (IsAdminUser,) - paginate_by = 100 For more complex cases you might also want to override various methods on the view class. For example. @@ -72,8 +71,6 @@ The following attributes are used to control pagination when used with list view * `pagination_class` - The pagination class that should be used when paginating list results. Defaults to the same value as the `DEFAULT_PAGINATION_CLASS` setting, which is `'rest_framework.pagination.PageNumberPagination'`. -Note that usage of the `paginate_by`, `paginate_by_param` and `page_kwarg` attributes are now pending deprecation. The `pagination_serializer_class` attribute and `DEFAULT_PAGINATION_SERIALIZER_CLASS` setting have been removed completely. Pagination settings should instead be controlled by overriding a pagination class and setting any configuration attributes there. See the pagination documentation for more details. - **Filtering**: * `filter_backends` - A list of filter backend classes that should be used for filtering the queryset. Defaults to the same value as the `DEFAULT_FILTER_BACKENDS` setting. @@ -223,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 @@ -333,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. @@ -386,7 +382,7 @@ The [django-rest-framework-bulk package][django-rest-framework-bulk] implements [Django Rest Multiple Models][django-rest-multiple-models] provides a generic view (and mixin) for sending multiple serialized models and/or querysets via a single API request. -[cite]: https://docs.djangoproject.com/en/dev/ref/class-based-views/#base-vs-generic-views +[cite]: https://docs.djangoproject.com/en/stable/ref/class-based-views/#base-vs-generic-views [GenericAPIView]: #genericapiview [ListModelMixin]: #listmodelmixin [CreateModelMixin]: #createmodelmixin diff --git a/docs/api-guide/metadata.md b/docs/api-guide/metadata.md index e12aeb7fd..de28ffd8a 100644 --- a/docs/api-guide/metadata.md +++ b/docs/api-guide/metadata.md @@ -104,6 +104,18 @@ Then configure your settings to use this custom class: 'DEFAULT_METADATA_CLASS': 'myproject.apps.core.MinimalMetadata' } +# Third party packages + +The following third party packages provide additional metadata implementations. + +## DRF-schema-adapter + +[drf-schema-adapter][drf-schema-adapter] is a set of tools that makes it easier to provide schema information to frontend frameworks and libraries. It provides a metadata mixin as well as 2 metadata classes and several adapters suitable to generate [json-schema][json-schema] as well as schema information readable by various libraries. + +You can also write your own adapter to work with your specific frontend. +If you wish to do so, it also provides an exporter that can export those schema information to json files. + [cite]: http://tools.ietf.org/html/rfc7231#section-4.3.7 [no-options]: https://www.mnot.net/blog/2012/10/29/NO_OPTIONS [json-schema]: http://json-schema.org/ +[drf-schema-adapter]: https://github.com/drf-forms/drf-schema-adapter diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index f704a3247..bc7a5602d 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 @@ -47,14 +50,14 @@ You can then apply your new style to a view using the `.pagination_class` attrib class BillingRecordsView(generics.ListAPIView): queryset = Billing.objects.all() - serializer = BillingRecordsSerializer + serializer_class = BillingRecordsSerializer pagination_class = LargeResultsSetPagination Or apply the style globally, using the `DEFAULT_PAGINATION_CLASS` settings key. For example: REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'apps.core.pagination.StandardResultsSetPagination' - } + } --- @@ -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. -[cite]: https://docs.djangoproject.com/en/dev/topics/pagination/ +## 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/stable/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 84fd6e5ac..7bf932d06 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 @@ -51,7 +51,7 @@ using the `APIView` class based views. return Response({'received data': request.data}) Or, if you're using the `@api_view` decorator with function based views. - + from rest_framework.decorators import api_view from rest_framework.decorators import parser_classes @@ -93,7 +93,9 @@ You will typically want to use both `FormParser` and `MultiPartParser` together Parses raw file upload content. The `request.data` property will be a dictionary with a single key `'file'` containing the uploaded file. -If the view used with `FileUploadParser` is called with a `filename` URL keyword argument, then that argument will be used as the filename. If it is called without a `filename` URL keyword argument, then the client must set the filename in the `Content-Disposition` HTTP header. For example `Content-Disposition: attachment; filename=upload.jpg`. +If the view used with `FileUploadParser` is called with a `filename` URL keyword argument, then that argument will be used as the filename. + +If it is called without a `filename` URL keyword argument, then the client must set the filename in the `Content-Disposition` HTTP header. For example `Content-Disposition: attachment; filename=upload.jpg`. **.media_type**: `*/*` @@ -105,6 +107,7 @@ If the view used with `FileUploadParser` is called with a `filename` URL keyword ##### Basic usage example: + # views.py class FileUploadView(views.APIView): parser_classes = (FileUploadParser,) @@ -115,6 +118,11 @@ If the view used with `FileUploadParser` is called with a `filename` URL keyword # ... return Response(status=204) + # urls.py + urlpatterns = [ + # ... + url(r'^upload/(?P